@bendyline/gezel-inference 0.0.0-stage → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +13 -2
- package/dist/cache/adapter.d.ts +115 -0
- package/dist/cache/adapter.js +23 -0
- package/dist/cache/budget.d.ts +15 -0
- package/dist/cache/budget.js +31 -0
- package/dist/cache/controller.d.ts +205 -0
- package/dist/cache/controller.js +377 -0
- package/dist/catalog/LICENSE +21 -0
- package/dist/catalog/chat-models/bt/btl4-35b-q4/manifest.json +146 -0
- package/dist/catalog/chat-models/bt/btl4-35b-q4/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/chat-models/bt/btl4-35b-q4/versions/1.0.1/manifest.json +15 -0
- package/dist/catalog/chat-models/bt/btl4-compact-iq2/manifest.json +146 -0
- package/dist/catalog/chat-models/bt/btl4-compact-iq2/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/chat-models/bt/btl4-compact-iq2/versions/1.0.1/manifest.json +15 -0
- package/dist/catalog/chat-models/bt/btl4-compact-iq2/versions/1.0.2/manifest.json +15 -0
- package/dist/catalog/chat-models/de/deepseek-r1-8b-q4/manifest.json +166 -0
- package/dist/catalog/chat-models/de/deepseek-r1-8b-q4/versions/1.1.0/manifest.json +62 -0
- package/dist/catalog/chat-models/de/deepseek-r1-8b-q4/versions/1.1.1/manifest.json +61 -0
- package/dist/catalog/chat-models/de/deepseek-v4-flash-284b-mxfp4/manifest.json +125 -0
- package/dist/catalog/chat-models/de/deepseek-v4-flash-284b-mxfp4/versions/1.0.0/manifest.json +27 -0
- package/dist/catalog/chat-models/de/deepseek-v4-flash-284b-q2/manifest.json +125 -0
- package/dist/catalog/chat-models/de/deepseek-v4-flash-284b-q2/versions/1.0.0/manifest.json +27 -0
- package/dist/catalog/chat-models/de/deepseek-v4-flash-284b-q2/versions/1.1.0/manifest.json +27 -0
- package/dist/catalog/chat-models/de/deepseek-v4-flash-284b-q2q4/manifest.json +126 -0
- package/dist/catalog/chat-models/de/deepseek-v4-flash-284b-q2q4/versions/1.0.0/manifest.json +27 -0
- package/dist/catalog/chat-models/de/deepseek-v4-flash-284b-q4/manifest.json +318 -0
- package/dist/catalog/chat-models/de/deepseek-v4-flash-284b-q4/versions/1.0.0/manifest.json +225 -0
- package/dist/catalog/chat-models/de/deepseek-v4-flash-284b-q4/versions/1.1.0/manifest.json +225 -0
- package/dist/catalog/chat-models/ge/gemma4-12b-q4/manifest.json +249 -0
- package/dist/catalog/chat-models/ge/gemma4-12b-q4/versions/1.0.0/manifest.json +70 -0
- package/dist/catalog/chat-models/ge/gemma4-12b-q4/versions/1.1.0/manifest.json +72 -0
- package/dist/catalog/chat-models/ge/gemma4-12b-q4/versions/1.1.2/manifest.json +78 -0
- package/dist/catalog/chat-models/ge/gemma4-12b-q4/versions/1.1.3/manifest.json +78 -0
- package/dist/catalog/chat-models/ge/gemma4-12b-q4/versions/1.2.0/manifest.json +98 -0
- package/dist/catalog/chat-models/ge/gemma4-12b-q8/manifest.json +228 -0
- package/dist/catalog/chat-models/ge/gemma4-12b-q8/versions/1.0.0/manifest.json +72 -0
- package/dist/catalog/chat-models/ge/gemma4-12b-q8/versions/1.0.2/manifest.json +78 -0
- package/dist/catalog/chat-models/ge/gemma4-12b-q8/versions/1.0.3/manifest.json +78 -0
- package/dist/catalog/chat-models/ge/gemma4-12b-q8/versions/1.1.0/manifest.json +78 -0
- package/dist/catalog/chat-models/ge/gemma4-26b-q4/manifest.json +220 -0
- package/dist/catalog/chat-models/ge/gemma4-26b-q4/versions/1.1.0/manifest.json +75 -0
- package/dist/catalog/chat-models/ge/gemma4-26b-q4/versions/1.2.0/manifest.json +75 -0
- package/dist/catalog/chat-models/ge/gemma4-26b-q4/versions/1.2.1/manifest.json +80 -0
- package/dist/catalog/chat-models/ge/gemma4-26b-q4/versions/1.2.2/manifest.json +80 -0
- package/dist/catalog/chat-models/ge/gemma4-26b-q4/versions/1.3.0/manifest.json +81 -0
- package/dist/catalog/chat-models/ge/gemma4-31b-q4/manifest.json +226 -0
- package/dist/catalog/chat-models/ge/gemma4-31b-q4/versions/1.1.0/manifest.json +80 -0
- package/dist/catalog/chat-models/ge/gemma4-31b-q4/versions/1.1.1/manifest.json +82 -0
- package/dist/catalog/chat-models/ge/gemma4-31b-q4/versions/1.2.0/manifest.json +82 -0
- package/dist/catalog/chat-models/ge/gemma4-31b-q4/versions/1.2.1/manifest.json +95 -0
- package/dist/catalog/chat-models/ge/gemma4-31b-q4/versions/1.2.2/manifest.json +95 -0
- package/dist/catalog/chat-models/ge/gemma4-31b-q4/versions/1.3.0/manifest.json +86 -0
- package/dist/catalog/chat-models/ge/gemma4-e2b-q4/manifest.json +213 -0
- package/dist/catalog/chat-models/ge/gemma4-e2b-q4/versions/1.1.0/manifest.json +67 -0
- package/dist/catalog/chat-models/ge/gemma4-e2b-q4/versions/1.1.2/manifest.json +75 -0
- package/dist/catalog/chat-models/ge/gemma4-e2b-q4/versions/1.2.0/manifest.json +71 -0
- package/dist/catalog/chat-models/ge/gemma4-e2b-q4/versions/1.2.1/manifest.json +71 -0
- package/dist/catalog/chat-models/ge/gemma4-e2b-q4/versions/1.3.0/manifest.json +71 -0
- package/dist/catalog/chat-models/ge/gemma4-e4b-q4/manifest.json +205 -0
- package/dist/catalog/chat-models/ge/gemma4-e4b-q4/versions/1.1.0/manifest.json +72 -0
- package/dist/catalog/chat-models/ge/gemma4-e4b-q4/versions/1.1.2/manifest.json +75 -0
- package/dist/catalog/chat-models/ge/gemma4-e4b-q4/versions/1.2.0/manifest.json +76 -0
- package/dist/catalog/chat-models/ge/gemma4-e4b-q4/versions/1.2.1/manifest.json +76 -0
- package/dist/catalog/chat-models/ge/gemma4-e4b-q4/versions/1.3.0/manifest.json +76 -0
- package/dist/catalog/chat-models/gl/glm-5.2-754b-q2/manifest.json +127 -0
- package/dist/catalog/chat-models/gl/glm-5.2-754b-q2/versions/1.0.0/manifest.json +20 -0
- package/dist/catalog/chat-models/gl/glm-5.3-flash-320b-q2/manifest.json +140 -0
- package/dist/catalog/chat-models/gl/glm-5.3-flash-320b-q2/versions/1.0.0/manifest.json +23 -0
- package/dist/catalog/chat-models/gl/glm-5.3-flash-320b-q2/versions/1.0.1/manifest.json +26 -0
- package/dist/catalog/chat-models/gp/gpt-oss-120b-q4/manifest.json +247 -0
- package/dist/catalog/chat-models/gp/gpt-oss-120b-q4/versions/1.0.0/manifest.json +137 -0
- package/dist/catalog/chat-models/gp/gpt-oss-120b-q4/versions/1.1.0/manifest.json +135 -0
- package/dist/catalog/chat-models/gp/gpt-oss-20b-q4/manifest.json +195 -0
- package/dist/catalog/chat-models/gp/gpt-oss-20b-q4/versions/1.1.0/manifest.json +72 -0
- package/dist/catalog/chat-models/gp/gpt-oss-20b-q4/versions/1.1.1/manifest.json +71 -0
- package/dist/catalog/chat-models/gr/granite4.2-30b-q4/manifest.json +200 -0
- package/dist/catalog/chat-models/gr/granite4.2-30b-q4/versions/1.0.0/manifest.json +72 -0
- package/dist/catalog/chat-models/gr/granite4.2-3b-q4/manifest.json +196 -0
- package/dist/catalog/chat-models/gr/granite4.2-3b-q4/versions/1.0.0/manifest.json +57 -0
- package/dist/catalog/chat-models/gr/granite4.2-8b-q4/manifest.json +189 -0
- package/dist/catalog/chat-models/gr/granite4.2-8b-q4/versions/1.0.0/manifest.json +57 -0
- package/dist/catalog/chat-models/index.json +1 -0
- package/dist/catalog/chat-models/la/laguna-s-2.1-118b-q4/manifest.json +235 -0
- package/dist/catalog/chat-models/la/laguna-s-2.1-118b-q4/versions/1.0.0/manifest.json +124 -0
- package/dist/catalog/chat-models/la/laguna-s-2.1-118b-q4/versions/1.0.1/manifest.json +124 -0
- package/dist/catalog/chat-models/la/laguna-s-2.1-118b-q4/versions/1.0.2/manifest.json +123 -0
- package/dist/catalog/chat-models/la/laguna-s-2.1-118b-q6/manifest.json +257 -0
- package/dist/catalog/chat-models/la/laguna-s-2.1-118b-q6/versions/1.0.0/manifest.json +145 -0
- package/dist/catalog/chat-models/la/laguna-s-2.1-118b-q6/versions/1.0.1/manifest.json +145 -0
- package/dist/catalog/chat-models/la/laguna-s-2.1-118b-q8/manifest.json +285 -0
- package/dist/catalog/chat-models/la/laguna-s-2.1-118b-q8/versions/1.0.0/manifest.json +174 -0
- package/dist/catalog/chat-models/la/laguna-s-2.1-118b-q8/versions/1.0.1/manifest.json +174 -0
- package/dist/catalog/chat-models/la/laguna-s-2.1-118b-q8/versions/1.0.2/manifest.json +173 -0
- package/dist/catalog/chat-models/lf/lfm2.5-2.6b-q4/manifest.json +162 -0
- package/dist/catalog/chat-models/lf/lfm2.5-2.6b-q4/versions/1.0.0/manifest.json +59 -0
- package/dist/catalog/chat-models/lf/lfm2.5-2.6b-q4/versions/1.0.1/manifest.json +58 -0
- package/dist/catalog/chat-models/ll/llama3.2-3b-q4/manifest.json +134 -0
- package/dist/catalog/chat-models/ll/llama3.2-3b-q4/versions/1.1.0/manifest.json +57 -0
- package/dist/catalog/chat-models/ll/llama3.2-3b-q4/versions/1.1.1/manifest.json +56 -0
- package/dist/catalog/chat-models/mi/minicpm5-1b-q4/manifest.json +156 -0
- package/dist/catalog/chat-models/mi/minicpm5-1b-q4/versions/1.0.0/manifest.json +57 -0
- package/dist/catalog/chat-models/mi/minicpm5-2b-q4/manifest.json +161 -0
- package/dist/catalog/chat-models/mi/minicpm5-2b-q4/versions/1.0.0/manifest.json +57 -0
- package/dist/catalog/chat-models/mi/mistral-7b-q4/manifest.json +159 -0
- package/dist/catalog/chat-models/mi/mistral-7b-q4/versions/1.1.0/manifest.json +62 -0
- package/dist/catalog/chat-models/mi/mistral-7b-q4/versions/1.1.1/manifest.json +61 -0
- package/dist/catalog/chat-models/mi/mistral-medium-3.5-128b-q4/manifest.json +275 -0
- package/dist/catalog/chat-models/mi/mistral-medium-3.5-128b-q4/versions/1.0.0/manifest.json +154 -0
- package/dist/catalog/chat-models/mi/mistral-medium-3.5-128b-q4/versions/1.0.1/manifest.json +153 -0
- package/dist/catalog/chat-models/mu/muse-glimmer-30b-q4/manifest.json +135 -0
- package/dist/catalog/chat-models/mu/muse-glimmer-30b-q4/versions/1.0.0/manifest.json +20 -0
- package/dist/catalog/chat-models/mu/muse-glimmer-30b-q4/versions/1.0.1/manifest.json +20 -0
- package/dist/catalog/chat-models/mu/muse-glimmer-30b-q4/versions/1.0.2/manifest.json +20 -0
- package/dist/catalog/chat-models/na/nanbeige4.2-3b-q4/manifest.json +138 -0
- package/dist/catalog/chat-models/na/nanbeige4.2-3b-q4/versions/1.0.0/manifest.json +14 -0
- package/dist/catalog/chat-models/ne/nemotron3-nano-30b-q4/manifest.json +212 -0
- package/dist/catalog/chat-models/ne/nemotron3-nano-30b-q4/versions/1.0.0/manifest.json +94 -0
- package/dist/catalog/chat-models/ne/nemotron3-nano-30b-q4/versions/1.0.1/manifest.json +94 -0
- package/dist/catalog/chat-models/ne/nemotron3-nano-30b-q4/versions/1.0.2/manifest.json +93 -0
- package/dist/catalog/chat-models/ne/nemotron3-super-120b-q4/manifest.json +135 -0
- package/dist/catalog/chat-models/ne/nemotron3-super-120b-q4/versions/1.0.0/manifest.json +30 -0
- package/dist/catalog/chat-models/ne/nemotron3-super-120b-q4/versions/1.0.1/manifest.json +30 -0
- package/dist/catalog/chat-models/ne/nemotron3.5-lightning-30b-q4/manifest.json +192 -0
- package/dist/catalog/chat-models/ne/nemotron3.5-lightning-30b-q4/versions/1.0.0/manifest.json +74 -0
- package/dist/catalog/chat-models/ne/nemotron3.5-lightning-30b-q4/versions/1.0.1/manifest.json +73 -0
- package/dist/catalog/chat-models/ne/nemotron3.5-lightning-30b-q8/manifest.json +207 -0
- package/dist/catalog/chat-models/ne/nemotron3.5-lightning-30b-q8/versions/1.0.0/manifest.json +89 -0
- package/dist/catalog/chat-models/ne/nemotron3.5-lightning-30b-q8/versions/1.0.1/manifest.json +88 -0
- package/dist/catalog/chat-models/or/ornith-35b-q4/manifest.json +224 -0
- package/dist/catalog/chat-models/or/ornith-35b-q4/versions/1.0.0/manifest.json +94 -0
- package/dist/catalog/chat-models/or/ornith-35b-q4/versions/1.0.1/manifest.json +93 -0
- package/dist/catalog/chat-models/or/ornith-35b-q8/manifest.json +244 -0
- package/dist/catalog/chat-models/or/ornith-35b-q8/versions/1.0.0/manifest.json +114 -0
- package/dist/catalog/chat-models/or/ornith-35b-q8/versions/1.0.1/manifest.json +113 -0
- package/dist/catalog/chat-models/or/ornith-9b-q4/manifest.json +215 -0
- package/dist/catalog/chat-models/or/ornith-9b-q4/versions/1.0.0/manifest.json +84 -0
- package/dist/catalog/chat-models/or/ornith-9b-q4/versions/1.0.1/manifest.json +83 -0
- package/dist/catalog/chat-models/or/ornith1.5-35b-a3b-q4/manifest.json +208 -0
- package/dist/catalog/chat-models/or/ornith1.5-35b-a3b-q4/versions/1.0.0/manifest.json +74 -0
- package/dist/catalog/chat-models/or/ornith1.5-35b-a3b-q8/manifest.json +223 -0
- package/dist/catalog/chat-models/or/ornith1.5-35b-a3b-q8/versions/1.0.0/manifest.json +89 -0
- package/dist/catalog/chat-models/or/ornith1.5-9b-q4/manifest.json +187 -0
- package/dist/catalog/chat-models/or/ornith1.5-9b-q4/versions/1.0.0/manifest.json +53 -0
- package/dist/catalog/chat-models/or/ornith1.5-9b-q4/versions/1.0.1/manifest.json +77 -0
- package/dist/catalog/chat-models/or/ornith1.5-9b-q8/manifest.json +192 -0
- package/dist/catalog/chat-models/or/ornith1.5-9b-q8/versions/1.0.0/manifest.json +58 -0
- package/dist/catalog/chat-models/or/ornith1.5-9b-q8/versions/1.0.1/manifest.json +82 -0
- package/dist/catalog/chat-models/qw/qwen3.5-0.8b-q4/manifest.json +192 -0
- package/dist/catalog/chat-models/qw/qwen3.5-0.8b-q4/versions/1.0.0/manifest.json +75 -0
- package/dist/catalog/chat-models/qw/qwen3.5-122b-a10b-q4/manifest.json +315 -0
- package/dist/catalog/chat-models/qw/qwen3.5-122b-a10b-q4/versions/1.0.0/manifest.json +164 -0
- package/dist/catalog/chat-models/qw/qwen3.5-122b-a10b-q4/versions/1.0.1/manifest.json +164 -0
- package/dist/catalog/chat-models/qw/qwen3.5-122b-a10b-q4/versions/1.0.2/manifest.json +164 -0
- package/dist/catalog/chat-models/qw/qwen3.5-122b-a10b-q4/versions/1.0.3/manifest.json +163 -0
- package/dist/catalog/chat-models/qw/qwen3.5-27b-q4/manifest.json +217 -0
- package/dist/catalog/chat-models/qw/qwen3.5-27b-q4/versions/1.0.0/manifest.json +90 -0
- package/dist/catalog/chat-models/qw/qwen3.5-2b-q4/manifest.json +194 -0
- package/dist/catalog/chat-models/qw/qwen3.5-2b-q4/versions/1.1.0/manifest.json +77 -0
- package/dist/catalog/chat-models/qw/qwen3.5-2b-q4/versions/1.1.2/manifest.json +76 -0
- package/dist/catalog/chat-models/qw/qwen3.5-2b-q4/versions/1.1.3/manifest.json +76 -0
- package/dist/catalog/chat-models/qw/qwen3.5-4b-q4/manifest.json +188 -0
- package/dist/catalog/chat-models/qw/qwen3.5-4b-q4/versions/1.1.0/manifest.json +77 -0
- package/dist/catalog/chat-models/qw/qwen3.5-4b-q4/versions/1.1.2/manifest.json +76 -0
- package/dist/catalog/chat-models/qw/qwen3.5-4b-q4/versions/1.1.3/manifest.json +76 -0
- package/dist/catalog/chat-models/qw/qwen3.5-9b-q4/manifest.json +205 -0
- package/dist/catalog/chat-models/qw/qwen3.5-9b-q4/versions/1.1.0/manifest.json +82 -0
- package/dist/catalog/chat-models/qw/qwen3.5-9b-q4/versions/1.1.2/manifest.json +81 -0
- package/dist/catalog/chat-models/qw/qwen3.5-9b-q4/versions/1.1.3/manifest.json +81 -0
- package/dist/catalog/chat-models/qw/qwen3.6-27b-q4/manifest.json +230 -0
- package/dist/catalog/chat-models/qw/qwen3.6-27b-q4/versions/1.1.0/manifest.json +92 -0
- package/dist/catalog/chat-models/qw/qwen3.6-27b-q4/versions/1.1.4/manifest.json +91 -0
- package/dist/catalog/chat-models/qw/qwen3.6-27b-q4/versions/1.1.5/manifest.json +91 -0
- package/dist/catalog/chat-models/qw/qwen3.6-27b-q4/versions/1.1.6/manifest.json +115 -0
- package/dist/catalog/chat-models/qw/qwen3.6-27b-q8/manifest.json +240 -0
- package/dist/catalog/chat-models/qw/qwen3.6-27b-q8/versions/1.0.0/manifest.json +102 -0
- package/dist/catalog/chat-models/qw/qwen3.6-27b-q8/versions/1.0.2/manifest.json +103 -0
- package/dist/catalog/chat-models/qw/qwen3.6-27b-q8/versions/1.0.3/manifest.json +103 -0
- package/dist/catalog/chat-models/qw/qwen3.6-27b-q8/versions/1.0.4/manifest.json +127 -0
- package/dist/catalog/chat-models/qw/qwen3.6-35b-a3b-q4/manifest.json +233 -0
- package/dist/catalog/chat-models/qw/qwen3.6-35b-a3b-q4/versions/1.0.0/manifest.json +92 -0
- package/dist/catalog/chat-models/qw/qwen3.6-35b-a3b-q4/versions/1.0.1/manifest.json +93 -0
- package/dist/catalog/chat-models/qw/qwen3.6-35b-a3b-q4/versions/1.0.2/manifest.json +93 -0
- package/dist/catalog/chat-models/qw/qwen3.6-35b-a3b-q4/versions/1.0.3/manifest.json +117 -0
- package/dist/catalog/chat-models/qw/qwen3.6-35b-a3b-q8/manifest.json +252 -0
- package/dist/catalog/chat-models/qw/qwen3.6-35b-a3b-q8/versions/1.0.0/manifest.json +112 -0
- package/dist/catalog/chat-models/qw/qwen3.6-35b-a3b-q8/versions/1.0.1/manifest.json +113 -0
- package/dist/catalog/chat-models/qw/qwen3.6-35b-a3b-q8/versions/1.0.2/manifest.json +113 -0
- package/dist/catalog/chat-models/qw/qwen3.6-35b-a3b-q8/versions/1.0.3/manifest.json +137 -0
- package/dist/catalog/chat-models/qw/qwen3.8-27b-iq1-s/manifest.json +195 -0
- package/dist/catalog/chat-models/qw/qwen3.8-27b-iq1-s/versions/1.0.0/manifest.json +19 -0
- package/dist/catalog/chat-models/qw/qwen3.8-27b-q2/manifest.json +194 -0
- package/dist/catalog/chat-models/qw/qwen3.8-27b-q2/versions/1.0.0/manifest.json +19 -0
- package/dist/catalog/chat-models/qw/qwen3.8-27b-q3/manifest.json +194 -0
- package/dist/catalog/chat-models/qw/qwen3.8-27b-q3/versions/1.0.0/manifest.json +19 -0
- package/dist/catalog/chat-models/qw/qwen3.8-27b-q3/versions/1.0.1/manifest.json +19 -0
- package/dist/catalog/chat-models/qw/qwen3.8-27b-q4/manifest.json +291 -0
- package/dist/catalog/chat-models/qw/qwen3.8-27b-q4/versions/1.0.0/manifest.json +20 -0
- package/dist/catalog/chat-models/qw/qwen3.8-27b-q4/versions/1.0.1/manifest.json +93 -0
- package/dist/catalog/chat-models/qw/qwen3.8-27b-q4/versions/1.0.2/manifest.json +92 -0
- package/dist/catalog/chat-models/qw/qwen3.8-27b-q4/versions/1.0.3/manifest.json +116 -0
- package/dist/catalog/chat-models/qw/qwen3.8-27b-q4/versions/1.0.4/manifest.json +116 -0
- package/dist/catalog/chat-models/qw/qwen3.8-27b-q4/versions/1.0.5/manifest.json +116 -0
- package/dist/catalog/chat-models/qw/qwen3.8-27b-q6/manifest.json +278 -0
- package/dist/catalog/chat-models/qw/qwen3.8-27b-q6/versions/1.0.0/manifest.json +20 -0
- package/dist/catalog/chat-models/qw/qwen3.8-27b-q6/versions/1.0.1/manifest.json +104 -0
- package/dist/catalog/chat-models/qw/qwen3.8-27b-q6/versions/1.0.2/manifest.json +103 -0
- package/dist/catalog/chat-models/qw/qwen3.8-27b-q6/versions/1.0.3/manifest.json +127 -0
- package/dist/catalog/chat-models/qw/qwen3.8-27b-q8/manifest.json +278 -0
- package/dist/catalog/chat-models/qw/qwen3.8-27b-q8/versions/1.0.0/manifest.json +113 -0
- package/dist/catalog/chat-models/qw/qwen3.8-27b-q8/versions/1.0.1/manifest.json +113 -0
- package/dist/catalog/chat-models/qw/qwen3.8-27b-q8/versions/1.0.2/manifest.json +137 -0
- package/dist/catalog/chat-models/qw/qwen3.8-flash-next-iq3/manifest.json +221 -0
- package/dist/catalog/chat-models/qw/qwen3.8-flash-next-iq3/versions/1.0.0/manifest.json +36 -0
- package/dist/catalog/chat-models/qw/qwen3.8-flash-next-iq4/manifest.json +429 -0
- package/dist/catalog/chat-models/qw/qwen3.8-flash-next-iq4/versions/1.0.0/manifest.json +36 -0
- package/dist/catalog/chat-models/qw/qwen3.8-flash-next-iq4/versions/1.1.0/manifest.json +205 -0
- package/dist/catalog/chat-models/qw/qwen3.8-flash-next-iq4/versions/1.1.1/manifest.json +244 -0
- package/dist/catalog/chat-models/qw/qwen3.8-flash-next-q2/manifest.json +203 -0
- package/dist/catalog/chat-models/qw/qwen3.8-flash-next-q2/versions/1.0.0/manifest.json +21 -0
- package/dist/catalog/chat-models/qw/qwen3.8-flash-next-q2/versions/1.0.1/manifest.json +31 -0
- package/dist/catalog/chat-models/qw/qwen3.8-flash-next-q2/versions/1.0.2/manifest.json +30 -0
- package/dist/catalog/chat-models/raw-index.json +1 -0
- package/dist/catalog/chat-models/ta/talkie-1930-13b-q4/manifest.json +79 -0
- package/dist/catalog/chat-models/ta/talkie-1930-13b-q4/versions/1.0.0/manifest.json +15 -0
- package/dist/catalog/chat-models/ta/talkie-1930-13b-q4/versions/1.0.1/manifest.json +15 -0
- package/dist/catalog/chat-models/te/ternary-bonsai-27b-q2/manifest.json +204 -0
- package/dist/catalog/chat-models/te/ternary-bonsai-27b-q2/versions/1.0.0/manifest.json +70 -0
- package/dist/catalog/chat-models/te/ternary-bonsai-27b-q2/versions/1.0.1/manifest.json +70 -0
- package/dist/catalog/chat-models/un/underdog-saluki-27b-iq2-mix/manifest.json +164 -0
- package/dist/catalog/chat-models/un/underdog-saluki-27b-iq2-mix/versions/1.0.0/manifest.json +14 -0
- package/dist/catalog/knowledge-catalogs/be/bendyline-how-to-books/manifest.json +26 -0
- package/dist/catalog/knowledge-catalogs/be/bendyline-how-to-books/versions/2026.10.8/manifest.json +8950 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-alaska-part-0001/manifest.json +24 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-alaska-part-0001/versions/2026.10.1/manifest.json +658 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-nevada-part-0001/manifest.json +24 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-nevada-part-0001/versions/2026.10.1/manifest.json +666 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-us-300-399-part-0001/manifest.json +24 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-us-300-399-part-0001/versions/2026.10.2/manifest.json +182 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-us-347-349/manifest.json +25 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-us-347-349/versions/2026.10.5/manifest.json +38 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-utah-part-0001/manifest.json +24 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-utah-part-0001/versions/2026.10.1/manifest.json +558 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-utah-part-0002/manifest.json +24 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-utah-part-0002/versions/2026.10.1/manifest.json +258 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-washington-part-0001/manifest.json +24 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-washington-part-0001/versions/2026.10.1/manifest.json +318 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-washington-part-0002/manifest.json +24 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-washington-part-0002/versions/2026.10.1/manifest.json +266 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-washington-part-0003/manifest.json +24 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-washington-part-0003/versions/2026.10.1/manifest.json +186 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-washington-part-0004/manifest.json +24 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-washington-part-0004/versions/2026.10.1/manifest.json +138 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-washington-part-0005/manifest.json +24 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-washington-part-0005/versions/2026.10.1/manifest.json +138 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-wyoming-part-0001/manifest.json +24 -0
- package/dist/catalog/knowledge-catalogs/ca/caselaw-wyoming-part-0001/versions/2026.10.3/manifest.json +626 -0
- package/dist/catalog/knowledge-catalogs/index.json +1 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-aspnet-core-en/manifest.json +22 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-aspnet-core-en/versions/2026.10.16/manifest.json +18270 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-azure-ai-platform-en/manifest.json +23 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-azure-ai-platform-en/versions/2026.10.16/manifest.json +1098 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-azure-apps-compute-en/manifest.json +23 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-azure-apps-compute-en/versions/2026.10.16/manifest.json +722 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-azure-data-aiot-en/manifest.json +23 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-azure-data-aiot-en/versions/2026.10.16/manifest.json +530 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-azure-management-hybrid-en/manifest.json +23 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-azure-management-hybrid-en/versions/2026.10.16/manifest.json +602 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-azure-network-security-en/manifest.json +23 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-azure-network-security-en/versions/2026.10.16/manifest.json +250 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-dotnet-en/manifest.json +22 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-dotnet-en/versions/2026.10.16/manifest.json +16794 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-entity-framework-en/manifest.json +22 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-entity-framework-en/versions/2026.10.16/manifest.json +1106 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-microsoft-365-en/manifest.json +22 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-microsoft-365-en/versions/2026.10.16/manifest.json +234 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-powershell-en/manifest.json +22 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-powershell-en/versions/2026.10.16/manifest.json +522 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-sql-server-en/manifest.json +22 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-sql-server-en/versions/2026.10.16/manifest.json +1850 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-windows-apps-en/manifest.json +22 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-windows-apps-en/versions/2026.10.16/manifest.json +1734 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-windows-server-en/manifest.json +22 -0
- package/dist/catalog/knowledge-catalogs/mi/microsoftdocs-windows-server-en/versions/2026.10.16/manifest.json +774 -0
- package/dist/catalog/knowledge-catalogs/raw-index.json +1 -0
- package/dist/catalog/knowledge-catalogs/re/regional-american-south/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/re/regional-american-south/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/re/regional-american-south/manifest.json +48 -0
- package/dist/catalog/knowledge-catalogs/re/regional-american-south/versions/2026.10.3/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-andes-amazon/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/re/regional-andes-amazon/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/re/regional-andes-amazon/manifest.json +48 -0
- package/dist/catalog/knowledge-catalogs/re/regional-andes-amazon/versions/2026.10.3/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-australia-nz/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/re/regional-australia-nz/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/re/regional-australia-nz/manifest.json +48 -0
- package/dist/catalog/knowledge-catalogs/re/regional-australia-nz/versions/2026.10.3/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-brazil/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/re/regional-brazil/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/re/regional-brazil/manifest.json +48 -0
- package/dist/catalog/knowledge-catalogs/re/regional-brazil/versions/2026.10.3/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-britain-ireland/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/re/regional-britain-ireland/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/re/regional-britain-ireland/manifest.json +48 -0
- package/dist/catalog/knowledge-catalogs/re/regional-britain-ireland/versions/2026.10.3/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-california-southwest/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/re/regional-california-southwest/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/re/regional-california-southwest/manifest.json +48 -0
- package/dist/catalog/knowledge-catalogs/re/regional-california-southwest/versions/2026.10.3/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-canada-alaska/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/re/regional-canada-alaska/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/re/regional-canada-alaska/manifest.json +48 -0
- package/dist/catalog/knowledge-catalogs/re/regional-canada-alaska/versions/2026.10.3/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-central-eastern-europe/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/re/regional-central-eastern-europe/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/re/regional-central-eastern-europe/manifest.json +48 -0
- package/dist/catalog/knowledge-catalogs/re/regional-central-eastern-europe/versions/2026.10.3/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-east-asia/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/re/regional-east-asia/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/re/regional-east-asia/manifest.json +48 -0
- package/dist/catalog/knowledge-catalogs/re/regional-east-asia/versions/2026.10.3/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-east-southern-africa/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/re/regional-east-southern-africa/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/re/regional-east-southern-africa/manifest.json +48 -0
- package/dist/catalog/knowledge-catalogs/re/regional-east-southern-africa/versions/2026.10.3/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-mediterranean/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/re/regional-mediterranean/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/re/regional-mediterranean/manifest.json +48 -0
- package/dist/catalog/knowledge-catalogs/re/regional-mediterranean/versions/2026.10.3/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-mexico-central-america/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/re/regional-mexico-central-america/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/re/regional-mexico-central-america/manifest.json +48 -0
- package/dist/catalog/knowledge-catalogs/re/regional-mexico-central-america/versions/2026.10.3/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-middle-east/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/re/regional-middle-east/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/re/regional-middle-east/manifest.json +48 -0
- package/dist/catalog/knowledge-catalogs/re/regional-middle-east/versions/2026.10.3/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-midwest/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/re/regional-midwest/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/re/regional-midwest/manifest.json +48 -0
- package/dist/catalog/knowledge-catalogs/re/regional-midwest/versions/2026.10.3/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-north-africa/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/re/regional-north-africa/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/re/regional-north-africa/manifest.json +48 -0
- package/dist/catalog/knowledge-catalogs/re/regional-north-africa/versions/2026.10.3/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-northeast/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/re/regional-northeast/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/re/regional-northeast/manifest.json +48 -0
- package/dist/catalog/knowledge-catalogs/re/regional-northeast/versions/2026.10.3/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-northern-eurasia/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/re/regional-northern-eurasia/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/re/regional-northern-eurasia/manifest.json +48 -0
- package/dist/catalog/knowledge-catalogs/re/regional-northern-eurasia/versions/2026.10.3/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-northern-europe/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/re/regional-northern-europe/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/re/regional-northern-europe/manifest.json +48 -0
- package/dist/catalog/knowledge-catalogs/re/regional-northern-europe/versions/2026.10.3/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-pacific-islands/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/re/regional-pacific-islands/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/re/regional-pacific-islands/manifest.json +48 -0
- package/dist/catalog/knowledge-catalogs/re/regional-pacific-islands/versions/2026.10.3/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-pacific-northwest/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/re/regional-pacific-northwest/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/re/regional-pacific-northwest/manifest.json +48 -0
- package/dist/catalog/knowledge-catalogs/re/regional-pacific-northwest/versions/2026.10.3/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-rockies-plains/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/re/regional-rockies-plains/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/re/regional-rockies-plains/manifest.json +48 -0
- package/dist/catalog/knowledge-catalogs/re/regional-rockies-plains/versions/2026.10.3/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-south-asia/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/re/regional-south-asia/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/re/regional-south-asia/manifest.json +48 -0
- package/dist/catalog/knowledge-catalogs/re/regional-south-asia/versions/2026.10.3/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-southeast-asia/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/re/regional-southeast-asia/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/re/regional-southeast-asia/manifest.json +48 -0
- package/dist/catalog/knowledge-catalogs/re/regional-southeast-asia/versions/2026.10.3/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-southern-cone/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/re/regional-southern-cone/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/re/regional-southern-cone/manifest.json +48 -0
- package/dist/catalog/knowledge-catalogs/re/regional-southern-cone/versions/2026.10.3/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-west-central-africa/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/re/regional-west-central-africa/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/re/regional-west-central-africa/manifest.json +48 -0
- package/dist/catalog/knowledge-catalogs/re/regional-west-central-africa/versions/2026.10.3/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/re/regional-western-europe/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/re/regional-western-europe/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/re/regional-western-europe/manifest.json +48 -0
- package/dist/catalog/knowledge-catalogs/re/regional-western-europe/versions/2026.10.3/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-arts/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-arts/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-arts/manifest.json +28 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-arts/versions/2026.4.2/manifest.json +53 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-arts/versions/2026.4.3/manifest.json +885 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-arts/versions/2026.4.5/manifest.json +157 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-astronomy/art.json +11 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-astronomy/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-astronomy/manifest.json +32 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-astronomy/versions/2026.4.2/manifest.json +69 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-astronomy/versions/2026.4.3/manifest.json +741 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-astronomy/versions/2026.4.5/manifest.json +173 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-biology/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-biology/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-biology/manifest.json +69 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-biology/versions/2026.4.4/manifest.json +217 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-biology/versions/2026.4.5/manifest.json +217 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-business/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-business/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-business/manifest.json +64 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-business/versions/2026.4.4/manifest.json +197 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-business/versions/2026.4.5/manifest.json +197 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-film-tv/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-film-tv/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-film-tv/manifest.json +64 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-film-tv/versions/2026.4.5/manifest.json +197 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-food-drink/art.json +11 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-food-drink/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-food-drink/manifest.json +29 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-food-drink/versions/2026.4.2/manifest.json +57 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-food-drink/versions/2026.4.3/manifest.json +925 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-food-drink/versions/2026.4.5/manifest.json +185 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-games/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-games/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-games/manifest.json +28 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-games/versions/2026.4.2/manifest.json +53 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-games/versions/2026.4.3/manifest.json +1665 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-games/versions/2026.4.5/manifest.json +133 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-language/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-language/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-language/manifest.json +29 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-language/versions/2026.4.2/manifest.json +57 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-language/versions/2026.4.3/manifest.json +1233 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-language/versions/2026.4.5/manifest.json +141 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-law/art.json +11 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-law/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-law/manifest.json +29 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-law/versions/2026.4.2/manifest.json +57 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-law/versions/2026.4.3/manifest.json +493 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-law/versions/2026.4.5/manifest.json +185 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-literature/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-literature/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-literature/manifest.json +63 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-literature/versions/2026.4.4/manifest.json +193 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-literature/versions/2026.4.5/manifest.json +193 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-mathematics/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-mathematics/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-mathematics/manifest.json +30 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-mathematics/versions/2026.4.2/manifest.json +61 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-mathematics/versions/2026.4.3/manifest.json +665 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-mathematics/versions/2026.4.5/manifest.json +177 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-medicine/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-medicine/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-medicine/manifest.json +29 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-medicine/versions/2026.4.2/manifest.json +57 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-medicine/versions/2026.4.3/manifest.json +745 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-medicine/versions/2026.4.5/manifest.json +185 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-military/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-military/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-military/manifest.json +62 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-military/versions/2026.4.4/manifest.json +189 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-military/versions/2026.4.5/manifest.json +189 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-music/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-music/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-music/manifest.json +64 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-music/versions/2026.4.5/manifest.json +197 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-politics/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-politics/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-politics/manifest.json +64 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-politics/versions/2026.4.4/manifest.json +197 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-politics/versions/2026.4.5/manifest.json +197 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-religion-philosophy/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-religion-philosophy/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-religion-philosophy/manifest.json +30 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-religion-philosophy/versions/2026.4.2/manifest.json +61 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-religion-philosophy/versions/2026.4.3/manifest.json +1253 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-religion-philosophy/versions/2026.4.5/manifest.json +189 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-science/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-science/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-science/manifest.json +29 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-science/versions/2026.4.2/manifest.json +57 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-science/versions/2026.4.3/manifest.json +1957 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-science/versions/2026.4.5/manifest.json +185 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-sports/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-sports/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-sports/manifest.json +63 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-sports/versions/2026.4.5/manifest.json +193 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-technology/art.json +11 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-technology/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-technology/manifest.json +45 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-technology/versions/2026.4.4/manifest.json +121 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-technology/versions/2026.4.5/manifest.json +173 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-transportation/art.json +12 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-transportation/logo.webp +0 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-transportation/manifest.json +62 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-transportation/versions/2026.4.4/manifest.json +189 -0
- package/dist/catalog/knowledge-catalogs/wi/wikipedia-transportation/versions/2026.4.5/manifest.json +189 -0
- package/dist/catalog/provenance.json +8 -0
- package/dist/chat/catalog-model-lookup.d.ts +21 -0
- package/dist/chat/catalog-model-lookup.js +48 -0
- package/dist/chat/local-model-tier.d.ts +1 -0
- package/dist/chat/local-model-tier.js +2 -0
- package/dist/chat/vision-capability.d.ts +1 -0
- package/dist/chat/vision-capability.js +3 -0
- package/dist/engine-components.d.ts +30 -0
- package/dist/engine-components.js +144 -0
- package/dist/engines/electron-native-reuse.d.ts +65 -0
- package/dist/engines/electron-native-reuse.js +305 -0
- package/dist/engines/native-file-manifest.json +1320 -0
- package/dist/engines/native-manifest.d.ts +70 -0
- package/dist/engines/native-manifest.js +98 -0
- package/dist/engines/registry.d.ts +88 -0
- package/dist/engines/registry.js +217 -0
- package/dist/engines/resolver.d.ts +121 -0
- package/dist/engines/resolver.js +894 -0
- package/dist/engines/signature.d.ts +85 -0
- package/dist/engines/signature.js +231 -0
- package/dist/fs/atomic.d.ts +53 -0
- package/dist/fs/atomic.js +202 -0
- package/dist/fs/config-store.d.ts +14 -0
- package/dist/fs/config-store.js +101 -0
- package/dist/fs/home-permissions.d.ts +18 -0
- package/dist/fs/home-permissions.js +27 -0
- package/dist/fs/safe-paths.d.ts +107 -0
- package/dist/fs/safe-paths.js +264 -0
- package/dist/fs/security-json.d.ts +13 -0
- package/dist/fs/security-json.js +67 -0
- package/dist/http/auth-context.d.ts +23 -0
- package/dist/http/auth-context.js +1 -0
- package/dist/http/engine-runtime-context.d.ts +30 -0
- package/dist/http/engine-runtime-context.js +1 -0
- package/dist/http/external-conversation-contract.d.ts +49 -0
- package/dist/http/external-conversation-contract.js +1 -0
- package/dist/http/external-conversation-headers.d.ts +11 -0
- package/dist/http/external-conversation-headers.js +11 -0
- package/dist/http/inference-context.d.ts +36 -0
- package/dist/http/inference-context.js +1 -0
- package/dist/http/model-inventory-cache.d.ts +8 -0
- package/dist/http/model-inventory-cache.js +8 -0
- package/dist/http/openai-compat/action-ledger.d.ts +15 -0
- package/dist/http/openai-compat/action-ledger.js +119 -0
- package/dist/http/openai-compat/apple-model.d.ts +3 -0
- package/dist/http/openai-compat/apple-model.js +44 -0
- package/dist/http/openai-compat/caller-owned-profile.d.ts +7 -0
- package/dist/http/openai-compat/caller-owned-profile.js +18 -0
- package/dist/http/openai-compat/chat-progress.d.ts +9 -0
- package/dist/http/openai-compat/chat-progress.js +69 -0
- package/dist/http/openai-compat/chat-target.d.ts +76 -0
- package/dist/http/openai-compat/chat-target.js +97 -0
- package/dist/http/openai-compat/embeddings.d.ts +37 -0
- package/dist/http/openai-compat/embeddings.js +86 -0
- package/dist/http/openai-compat/request-tuning.d.ts +35 -0
- package/dist/http/openai-compat/request-tuning.js +87 -0
- package/dist/http/openai-compat/responses-streaming.d.ts +351 -0
- package/dist/http/openai-compat/responses-streaming.js +717 -0
- package/dist/http/openai-compat/responses-translate.d.ts +215 -0
- package/dist/http/openai-compat/responses-translate.js +377 -0
- package/dist/http/openai-compat/streaming-telemetry.d.ts +30 -0
- package/dist/http/openai-compat/streaming-telemetry.js +88 -0
- package/dist/http/openai-compat/streaming.d.ts +141 -0
- package/dist/http/openai-compat/streaming.js +687 -0
- package/dist/http/openai-compat/tool-call-stream-filter.d.ts +33 -0
- package/dist/http/openai-compat/tool-call-stream-filter.js +110 -0
- package/dist/http/openai-compat/translate.d.ts +254 -0
- package/dist/http/openai-compat/translate.js +485 -0
- package/dist/http/routes/engines.d.ts +13 -0
- package/dist/http/routes/engines.js +366 -0
- package/dist/http/routes/machine-engine-proxy.d.ts +26 -0
- package/dist/http/routes/machine-engine-proxy.js +43 -0
- package/dist/http/routes/model-bundles.d.ts +4 -0
- package/dist/http/routes/model-bundles.js +190 -0
- package/dist/http/routes/v1-chat.d.ts +72 -0
- package/dist/http/routes/v1-chat.js +645 -0
- package/dist/http/routes/v1-embeddings.d.ts +3 -0
- package/dist/http/routes/v1-embeddings.js +82 -0
- package/dist/http/routes/v1-knowledge.d.ts +4 -0
- package/dist/http/routes/v1-knowledge.js +304 -0
- package/dist/http/routes/v1-models-ensure.d.ts +32 -0
- package/dist/http/routes/v1-models-ensure.js +122 -0
- package/dist/http/routes/v1-models.d.ts +8 -0
- package/dist/http/routes/v1-models.js +189 -0
- package/dist/http/routes/v1-responses.d.ts +20 -0
- package/dist/http/routes/v1-responses.js +327 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/inference-service.d.ts +28 -0
- package/dist/inference-service.js +275 -0
- package/dist/knowledge/admission.d.ts +3 -0
- package/dist/knowledge/admission.js +22 -0
- package/dist/knowledge/bundled-catalog.d.ts +2 -0
- package/dist/knowledge/bundled-catalog.js +2 -0
- package/dist/knowledge/catalog-host.d.ts +103 -0
- package/dist/knowledge/catalog-host.js +180 -0
- package/dist/knowledge/catalog-source.d.ts +22 -0
- package/dist/knowledge/catalog-source.js +35 -0
- package/dist/knowledge/install.d.ts +55 -0
- package/dist/knowledge/install.js +274 -0
- package/dist/knowledge/manager.d.ts +287 -0
- package/dist/knowledge/manager.js +1540 -0
- package/dist/knowledge/registry.d.ts +35 -0
- package/dist/knowledge/registry.js +123 -0
- package/dist/knowledge/search-worker.d.ts +17 -0
- package/dist/knowledge/search-worker.js +36 -0
- package/dist/knowledge/shared-install-contract.d.ts +19 -0
- package/dist/knowledge/shared-install-contract.js +1 -0
- package/dist/knowledge/spatial-query.d.ts +11 -0
- package/dist/knowledge/spatial-query.js +33 -0
- package/dist/knowledge/trust-anchors.d.ts +11 -0
- package/dist/knowledge/trust-anchors.js +51 -0
- package/dist/knowledge/vector-floors.d.ts +44 -0
- package/dist/knowledge/vector-floors.js +92 -0
- package/dist/knowledge/worker-host.d.ts +9 -0
- package/dist/knowledge/worker-host.js +179 -0
- package/dist/kokoro-lexicon/LICENSE +202 -0
- package/dist/kokoro-lexicon/lexicon-gb-en.txt.gz +0 -0
- package/dist/kokoro-lexicon/lexicon-us-en.txt.gz +0 -0
- package/dist/kokoro-lexicon/manifest.json +23 -0
- package/dist/machine-engine/inspect-runtime.d.ts +3 -0
- package/dist/machine-engine/inspect-runtime.js +48 -0
- package/dist/machine-engine/knowledge-assets.d.ts +79 -0
- package/dist/machine-engine/knowledge-assets.js +505 -0
- package/dist/memory/embed-core.d.ts +131 -0
- package/dist/memory/embed-core.js +398 -0
- package/dist/memory/embed-worker.d.ts +23 -0
- package/dist/memory/embed-worker.js +49 -0
- package/dist/memory/embeddings.d.ts +105 -0
- package/dist/memory/embeddings.js +467 -0
- package/dist/model-profile/behaviors/tools-mlx-template-fix.d.ts +1 -0
- package/dist/model-profile/behaviors/tools-mlx-template-fix.js +2 -0
- package/dist/model-profile/behaviors/turn-ramble-detection.d.ts +1 -0
- package/dist/model-profile/behaviors/turn-ramble-detection.js +2 -0
- package/dist/model-profile/defaults.d.ts +1 -0
- package/dist/model-profile/defaults.js +2 -0
- package/dist/model-profile/index.d.ts +9 -0
- package/dist/model-profile/index.js +7 -0
- package/dist/model-profile/registry.d.ts +15 -0
- package/dist/model-profile/registry.js +18 -0
- package/dist/model-profile/runtime.d.ts +48 -0
- package/dist/model-profile/runtime.js +131 -0
- package/dist/model-profile/tool-grammar.d.ts +1 -0
- package/dist/model-profile/tool-grammar.js +2 -0
- package/dist/model-profile/tuning.d.ts +19 -0
- package/dist/model-profile/tuning.js +21 -0
- package/dist/model-profile/types.d.ts +1 -0
- package/dist/model-profile/types.js +2 -0
- package/dist/models/bundle-storage.d.ts +28 -0
- package/dist/models/bundle-storage.js +80 -0
- package/dist/models/catalog-drift.d.ts +48 -0
- package/dist/models/catalog-drift.js +94 -0
- package/dist/models/catalog-payload-identity.d.ts +107 -0
- package/dist/models/catalog-payload-identity.js +254 -0
- package/dist/models/ensure.d.ts +125 -0
- package/dist/models/ensure.js +205 -0
- package/dist/models/gezmodel.d.ts +47 -0
- package/dist/models/gezmodel.js +742 -0
- package/dist/models/install-registry.d.ts +101 -0
- package/dist/models/install-registry.js +216 -0
- package/dist/models/model-id.d.ts +22 -0
- package/dist/models/model-id.js +40 -0
- package/dist/models/storage-roots.d.ts +274 -0
- package/dist/models/storage-roots.js +865 -0
- package/dist/native-release-entry.d.ts +16 -0
- package/dist/native-release-entry.js +16 -0
- package/dist/perf/redact-path.d.ts +2 -0
- package/dist/perf/redact-path.js +27 -0
- package/dist/perf/responsiveness.d.ts +70 -0
- package/dist/perf/responsiveness.js +560 -0
- package/dist/providers/active-install-message.d.ts +42 -0
- package/dist/providers/active-install-message.js +49 -0
- package/dist/providers/apple-foundation-models/binary.d.ts +4 -0
- package/dist/providers/apple-foundation-models/binary.js +18 -0
- package/dist/providers/apple-foundation-models/helper.d.ts +66 -0
- package/dist/providers/apple-foundation-models/helper.js +262 -0
- package/dist/providers/apple-foundation-models/provider.d.ts +82 -0
- package/dist/providers/apple-foundation-models/provider.js +389 -0
- package/dist/providers/apple-foundation-models/status.d.ts +9 -0
- package/dist/providers/apple-foundation-models/status.js +49 -0
- package/dist/providers/audio/kokoro-frontend.d.ts +50 -0
- package/dist/providers/audio/kokoro-frontend.js +113 -0
- package/dist/providers/audio/managed-speech-model.d.ts +9 -0
- package/dist/providers/audio/managed-speech-model.js +86 -0
- package/dist/providers/audio/types.d.ts +202 -0
- package/dist/providers/audio/types.js +17 -0
- package/dist/providers/audio/whisper-cpp.d.ts +99 -0
- package/dist/providers/audio/whisper-cpp.js +420 -0
- package/dist/providers/audio-persister-contract.d.ts +13 -0
- package/dist/providers/audio-persister-contract.js +1 -0
- package/dist/providers/catalog-model-config.d.ts +63 -0
- package/dist/providers/catalog-model-config.js +95 -0
- package/dist/providers/code-block-salvage.d.ts +1 -0
- package/dist/providers/code-block-salvage.js +2 -0
- package/dist/providers/codex-cli/reasoning.d.ts +8 -0
- package/dist/providers/codex-cli/reasoning.js +17 -0
- package/dist/providers/complete-tool-call.d.ts +16 -0
- package/dist/providers/complete-tool-call.js +25 -0
- package/dist/providers/constrained-turn.d.ts +1 -0
- package/dist/providers/constrained-turn.js +2 -0
- package/dist/providers/deliverable-read-pacing.d.ts +1 -0
- package/dist/providers/deliverable-read-pacing.js +2 -0
- package/dist/providers/direct-file-work-prompt.d.ts +1 -0
- package/dist/providers/direct-file-work-prompt.js +2 -0
- package/dist/providers/ds4/build-provider.d.ts +100 -0
- package/dist/providers/ds4/build-provider.js +539 -0
- package/dist/providers/ds4/cache-adapter.d.ts +42 -0
- package/dist/providers/ds4/cache-adapter.js +110 -0
- package/dist/providers/ds4/dspark.d.ts +83 -0
- package/dist/providers/ds4/dspark.js +107 -0
- package/dist/providers/ds4/mtp.d.ts +32 -0
- package/dist/providers/ds4/mtp.js +46 -0
- package/dist/providers/ds4/provider.d.ts +67 -0
- package/dist/providers/ds4/provider.js +104 -0
- package/dist/providers/ds4/residency.d.ts +143 -0
- package/dist/providers/ds4/residency.js +139 -0
- package/dist/providers/ds4/stdout-parser.d.ts +47 -0
- package/dist/providers/ds4/stdout-parser.js +134 -0
- package/dist/providers/duplicate-tool-calls.d.ts +1 -0
- package/dist/providers/duplicate-tool-calls.js +2 -0
- package/dist/providers/file-repair-policy.d.ts +1 -0
- package/dist/providers/file-repair-policy.js +2 -0
- package/dist/providers/gpu-arbiter.d.ts +159 -0
- package/dist/providers/gpu-arbiter.js +301 -0
- package/dist/providers/image-persister-contract.d.ts +15 -0
- package/dist/providers/image-persister-contract.js +1 -0
- package/dist/providers/llama-cpp/build-metadata.d.ts +13 -0
- package/dist/providers/llama-cpp/build-metadata.js +39 -0
- package/dist/providers/llama-cpp/build-provider.d.ts +66 -0
- package/dist/providers/llama-cpp/build-provider.js +1491 -0
- package/dist/providers/llama-cpp/cache-adapter.d.ts +242 -0
- package/dist/providers/llama-cpp/cache-adapter.js +689 -0
- package/dist/providers/llama-cpp/devices.d.ts +130 -0
- package/dist/providers/llama-cpp/devices.js +256 -0
- package/dist/providers/llama-cpp/disk-cache.d.ts +8 -0
- package/dist/providers/llama-cpp/disk-cache.js +112 -0
- package/dist/providers/llama-cpp/engine-flags.d.ts +181 -0
- package/dist/providers/llama-cpp/engine-flags.js +279 -0
- package/dist/providers/llama-cpp/gguf-metadata-async.d.ts +11 -0
- package/dist/providers/llama-cpp/gguf-metadata-async.js +180 -0
- package/dist/providers/llama-cpp/gguf-metadata-worker.d.ts +7 -0
- package/dist/providers/llama-cpp/gguf-metadata-worker.js +27 -0
- package/dist/providers/llama-cpp/gguf-metadata.d.ts +198 -0
- package/dist/providers/llama-cpp/gguf-metadata.js +615 -0
- package/dist/providers/llama-cpp/gguf-quantization-backfill.d.ts +32 -0
- package/dist/providers/llama-cpp/gguf-quantization-backfill.js +75 -0
- package/dist/providers/llama-cpp/index.d.ts +10 -0
- package/dist/providers/llama-cpp/index.js +13 -0
- package/dist/providers/llama-cpp/kv-cache-type.d.ts +118 -0
- package/dist/providers/llama-cpp/kv-cache-type.js +140 -0
- package/dist/providers/llama-cpp/log.d.ts +92 -0
- package/dist/providers/llama-cpp/log.js +280 -0
- package/dist/providers/llama-cpp/models.d.ts +389 -0
- package/dist/providers/llama-cpp/models.js +1011 -0
- package/dist/providers/llama-cpp/offload-planner.d.ts +339 -0
- package/dist/providers/llama-cpp/offload-planner.js +524 -0
- package/dist/providers/llama-cpp/provider.d.ts +493 -0
- package/dist/providers/llama-cpp/provider.js +862 -0
- package/dist/providers/llama-cpp/reasoning-launch.d.ts +7 -0
- package/dist/providers/llama-cpp/reasoning-launch.js +14 -0
- package/dist/providers/llama-cpp/spec-draft.d.ts +44 -0
- package/dist/providers/llama-cpp/spec-draft.js +44 -0
- package/dist/providers/llama-cpp/stdout-parser.d.ts +70 -0
- package/dist/providers/llama-cpp/stdout-parser.js +197 -0
- package/dist/providers/local-tool-call-salvage.d.ts +1 -0
- package/dist/providers/local-tool-call-salvage.js +2 -0
- package/dist/providers/local-turn-policy.d.ts +1 -0
- package/dist/providers/local-turn-policy.js +2 -0
- package/dist/providers/mlx/async-file-handoff.d.ts +11 -0
- package/dist/providers/mlx/async-file-handoff.js +25 -0
- package/dist/providers/mlx/build-provider.d.ts +65 -0
- package/dist/providers/mlx/build-provider.js +717 -0
- package/dist/providers/mlx/cache-adapter.d.ts +161 -0
- package/dist/providers/mlx/cache-adapter.js +445 -0
- package/dist/providers/mlx/chat-protocol.d.ts +48 -0
- package/dist/providers/mlx/chat-protocol.js +8 -0
- package/dist/providers/mlx/config-parser.d.ts +29 -0
- package/dist/providers/mlx/config-parser.js +81 -0
- package/dist/providers/mlx/engine-gate.d.ts +51 -0
- package/dist/providers/mlx/engine-gate.js +207 -0
- package/dist/providers/mlx/engine-hold.d.ts +41 -0
- package/dist/providers/mlx/engine-hold.js +59 -0
- package/dist/providers/mlx/engine-log-router.d.ts +84 -0
- package/dist/providers/mlx/engine-log-router.js +137 -0
- package/dist/providers/mlx/engine-stats-sampler.d.ts +36 -0
- package/dist/providers/mlx/engine-stats-sampler.js +59 -0
- package/dist/providers/mlx/index.d.ts +10 -0
- package/dist/providers/mlx/index.js +10 -0
- package/dist/providers/mlx/log.d.ts +55 -0
- package/dist/providers/mlx/log.js +157 -0
- package/dist/providers/mlx/model-geometry.d.ts +35 -0
- package/dist/providers/mlx/model-geometry.js +118 -0
- package/dist/providers/mlx/models.d.ts +289 -0
- package/dist/providers/mlx/models.js +1079 -0
- package/dist/providers/mlx/provider.d.ts +404 -0
- package/dist/providers/mlx/provider.js +3017 -0
- package/dist/providers/mlx/python/cache_persist.py +348 -0
- package/dist/providers/mlx/python/cache_seed.py +402 -0
- package/dist/providers/mlx/python/gezel_mlx_server.py +4491 -0
- package/dist/providers/mlx/python/lfm2_compat.py +55 -0
- package/dist/providers/mlx/python/qwen3_5_text_compat.py +60 -0
- package/dist/providers/mlx/python/qwen4_ple.py +159 -0
- package/dist/providers/mlx/python/spec_decode.py +775 -0
- package/dist/providers/mlx/python/template_stability.py +133 -0
- package/dist/providers/mlx/python/think_budget.py +113 -0
- package/dist/providers/mlx/python/tool_args_json.py +297 -0
- package/dist/providers/mlx/python/tool_call_stream.py +265 -0
- package/dist/providers/mlx/python/tool_grammar.py +794 -0
- package/dist/providers/mlx/python/vision_inputs.py +64 -0
- package/dist/providers/mlx/python/wave_policy.py +147 -0
- package/dist/providers/mlx/reasoning-stream.d.ts +85 -0
- package/dist/providers/mlx/reasoning-stream.js +196 -0
- package/dist/providers/mlx/request-shape.d.ts +34 -0
- package/dist/providers/mlx/request-shape.js +42 -0
- package/dist/providers/mlx/runtime-diagnostics.d.ts +29 -0
- package/dist/providers/mlx/runtime-diagnostics.js +162 -0
- package/dist/providers/mlx/spec-drafter.d.ts +25 -0
- package/dist/providers/mlx/spec-drafter.js +73 -0
- package/dist/providers/mlx/stdout-parser.d.ts +60 -0
- package/dist/providers/mlx/stdout-parser.js +254 -0
- package/dist/providers/mlx/textual-tool-call-salvage.d.ts +46 -0
- package/dist/providers/mlx/textual-tool-call-salvage.js +396 -0
- package/dist/providers/mlx/tool-call-protocol.d.ts +65 -0
- package/dist/providers/mlx/tool-call-protocol.js +140 -0
- package/dist/providers/mlx/tool-call-stripper.d.ts +68 -0
- package/dist/providers/mlx/tool-call-stripper.js +153 -0
- package/dist/providers/mlx/tool-image-retention.d.ts +23 -0
- package/dist/providers/mlx/tool-image-retention.js +43 -0
- package/dist/providers/mlx/turn-telemetry.d.ts +22 -0
- package/dist/providers/mlx/turn-telemetry.js +45 -0
- package/dist/providers/mlx/venv.d.ts +91 -0
- package/dist/providers/mlx/venv.js +111 -0
- package/dist/providers/mlx/vision-mode.d.ts +92 -0
- package/dist/providers/mlx/vision-mode.js +187 -0
- package/dist/providers/mlx/vision.d.ts +2 -0
- package/dist/providers/mlx/vision.js +12 -0
- package/dist/providers/mlx/wire-transcript.d.ts +14 -0
- package/dist/providers/mlx/wire-transcript.js +43 -0
- package/dist/providers/native/capacity-broker.d.ts +812 -0
- package/dist/providers/native/capacity-broker.js +1058 -0
- package/dist/providers/native/capacity-budget.d.ts +133 -0
- package/dist/providers/native/capacity-budget.js +265 -0
- package/dist/providers/native/device-capacity-ledger.d.ts +31 -0
- package/dist/providers/native/device-capacity-ledger.js +196 -0
- package/dist/providers/native/device-capacity.d.ts +84 -0
- package/dist/providers/native/device-capacity.js +431 -0
- package/dist/providers/native/engine-api-key.d.ts +57 -0
- package/dist/providers/native/engine-api-key.js +72 -0
- package/dist/providers/native/engine-key.d.ts +18 -0
- package/dist/providers/native/engine-key.js +41 -0
- package/dist/providers/native/engine-router.d.ts +145 -0
- package/dist/providers/native/engine-router.js +191 -0
- package/dist/providers/native/gpu-panic-guard.d.ts +54 -0
- package/dist/providers/native/gpu-panic-guard.js +59 -0
- package/dist/providers/native/inference.d.ts +33 -0
- package/dist/providers/native/inference.js +49 -0
- package/dist/providers/native/local-engine-runtime.d.ts +412 -0
- package/dist/providers/native/local-engine-runtime.js +1714 -0
- package/dist/providers/native/measured-budget.d.ts +32 -0
- package/dist/providers/native/measured-budget.js +54 -0
- package/dist/providers/native/port.d.ts +11 -0
- package/dist/providers/native/port.js +29 -0
- package/dist/providers/native/provider-pool.d.ts +474 -0
- package/dist/providers/native/provider-pool.js +1166 -0
- package/dist/providers/native/supervisor.d.ts +503 -0
- package/dist/providers/native/supervisor.js +1466 -0
- package/dist/providers/observation-context.d.ts +16 -0
- package/dist/providers/observation-context.js +1 -0
- package/dist/providers/openai-compatible/sse.d.ts +1 -0
- package/dist/providers/openai-compatible/sse.js +2 -0
- package/dist/providers/patient-fetch.d.ts +36 -0
- package/dist/providers/patient-fetch.js +44 -0
- package/dist/providers/project-macro-loop-bail.d.ts +7 -0
- package/dist/providers/project-macro-loop-bail.js +21 -0
- package/dist/providers/prose-document-salvage.d.ts +1 -0
- package/dist/providers/prose-document-salvage.js +2 -0
- package/dist/providers/provider-disposal.d.ts +1 -0
- package/dist/providers/provider-disposal.js +2 -0
- package/dist/providers/queue.d.ts +15 -0
- package/dist/providers/queue.js +23 -0
- package/dist/providers/ramble-abort-message.d.ts +1 -0
- package/dist/providers/ramble-abort-message.js +2 -0
- package/dist/providers/ramble-detector.d.ts +1 -0
- package/dist/providers/ramble-detector.js +2 -0
- package/dist/providers/reasoning-depth.d.ts +1 -0
- package/dist/providers/reasoning-depth.js +2 -0
- package/dist/providers/required-input-reads.d.ts +1 -0
- package/dist/providers/required-input-reads.js +2 -0
- package/dist/providers/session-defaults.d.ts +13 -0
- package/dist/providers/session-defaults.js +49 -0
- package/dist/providers/session-hooks.d.ts +26 -0
- package/dist/providers/session-hooks.js +1 -0
- package/dist/providers/streaming-session.d.ts +1 -0
- package/dist/providers/streaming-session.js +2 -0
- package/dist/providers/terminal-tool-policy.d.ts +1 -0
- package/dist/providers/terminal-tool-policy.js +2 -0
- package/dist/providers/tool-arg-schema-coercion.d.ts +1 -0
- package/dist/providers/tool-arg-schema-coercion.js +2 -0
- package/dist/providers/tool-executor.d.ts +12 -0
- package/dist/providers/tool-executor.js +20 -0
- package/dist/providers/tool-failure-tracker.d.ts +1 -0
- package/dist/providers/tool-failure-tracker.js +2 -0
- package/dist/providers/tool-repeat-tracker.d.ts +1 -0
- package/dist/providers/tool-repeat-tracker.js +2 -0
- package/dist/providers/turn-timeout.d.ts +1 -0
- package/dist/providers/turn-timeout.js +1 -0
- package/dist/providers/types.d.ts +834 -0
- package/dist/providers/types.js +42 -0
- package/dist/providers/usage-builder.d.ts +1 -0
- package/dist/providers/usage-builder.js +2 -0
- package/dist/providers/vision-capability.d.ts +109 -0
- package/dist/providers/vision-capability.js +137 -0
- package/dist/python/mlx-runtime-status-bus.d.ts +39 -0
- package/dist/python/mlx-runtime-status-bus.js +27 -0
- package/dist/python/uv-runtime.d.ts +176 -0
- package/dist/python/uv-runtime.js +492 -0
- package/dist/relevance/install.d.ts +28 -0
- package/dist/relevance/install.js +114 -0
- package/dist/relevance/manager.d.ts +79 -0
- package/dist/relevance/manager.js +211 -0
- package/dist/relevance/registry.d.ts +11 -0
- package/dist/relevance/registry.js +158 -0
- package/dist/relevance/relevance-core.d.ts +67 -0
- package/dist/relevance/relevance-core.js +189 -0
- package/dist/relevance/relevance-model.d.ts +42 -0
- package/dist/relevance/relevance-model.js +292 -0
- package/dist/relevance/relevance-worker.d.ts +20 -0
- package/dist/relevance/relevance-worker.js +57 -0
- package/dist/relevance/settings.d.ts +30 -0
- package/dist/relevance/settings.js +81 -0
- package/dist/remotes/identity.d.ts +121 -0
- package/dist/remotes/identity.js +348 -0
- package/dist/remotes/pinned-fetch.d.ts +45 -0
- package/dist/remotes/pinned-fetch.js +193 -0
- package/dist/remotes/types.d.ts +54 -0
- package/dist/remotes/types.js +1 -0
- package/dist/runtime-lock.d.ts +29 -0
- package/dist/runtime-lock.js +85 -0
- package/dist/search/relevance-stage.d.ts +81 -0
- package/dist/search/relevance-stage.js +177 -0
- package/dist/secrets/types.d.ts +63 -0
- package/dist/secrets/types.js +64 -0
- package/dist/speech.d.ts +1 -0
- package/dist/speech.js +1 -0
- package/dist/system/darwin-memory.d.ts +26 -0
- package/dist/system/darwin-memory.js +81 -0
- package/dist/system/gezel-process-memory.d.ts +55 -0
- package/dist/system/gezel-process-memory.js +156 -0
- package/dist/system/memory.d.ts +154 -0
- package/dist/system/memory.js +501 -0
- package/dist/transformers-cache.d.ts +112 -0
- package/dist/transformers-cache.js +392 -0
- package/dist/utils/disk-space.d.ts +55 -0
- package/dist/utils/disk-space.js +72 -0
- package/dist/utils/download-common.d.ts +138 -0
- package/dist/utils/download-common.js +196 -0
- package/dist/utils/download-with-retry.d.ts +85 -0
- package/dist/utils/download-with-retry.js +522 -0
- package/dist/utils/extract-zip.d.ts +22 -0
- package/dist/utils/extract-zip.js +103 -0
- package/dist/utils/retire-model-worker.d.ts +15 -0
- package/dist/utils/retire-model-worker.js +24 -0
- package/dist/utils/service-worker-entry.d.ts +59 -0
- package/dist/utils/service-worker-entry.js +69 -0
- package/dist/utils/xet-download.d.ts +40 -0
- package/dist/utils/xet-download.js +526 -0
- package/dist/utils/xet.d.ts +86 -0
- package/dist/utils/xet.js +201 -0
- package/package.json +103 -3
- package/scripts/embedding-packaging.cjs +264 -0
- package/scripts/embedding-packaging.d.cts +16 -0
|
@@ -0,0 +1,1466 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NativeEngineSupervisor — lifecycle for a native engine child process
|
|
3
|
+
* (e.g. `sd-server`, `llama-server`).
|
|
4
|
+
*
|
|
5
|
+
* Responsibilities:
|
|
6
|
+
* - **Lazy start** the engine on the first `ensureRunning()` call, not
|
|
7
|
+
* at service boot. No VRAM is held until something actually asks
|
|
8
|
+
* for work.
|
|
9
|
+
* - **Idle timeout**: after `idleTimeoutMs` with no activity the
|
|
10
|
+
* engine is SIGTERM'd to free GPU/VRAM. Fresh activity lazy-starts
|
|
11
|
+
* again.
|
|
12
|
+
* - **Health watch**: every `healthIntervalMs` probe `/health`. After
|
|
13
|
+
* three consecutive failures the process is killed and a restart
|
|
14
|
+
* is attempted. Restart budget is 3 attempts in 60s, matching the
|
|
15
|
+
* gezeld supervisor's policy.
|
|
16
|
+
*
|
|
17
|
+
* Intentionally engine-agnostic — callers (`StableDiffusionCppProvider`,
|
|
18
|
+
* `LlamaCppProvider`, future STT/TTS providers) ask `ensureRunning()`
|
|
19
|
+
* before each request, mark activity via `markUsed()`, and otherwise
|
|
20
|
+
* know nothing about subprocess plumbing. Tests inject a `spawn` hook
|
|
21
|
+
* so we can drive the lifecycle without a real binary.
|
|
22
|
+
*/
|
|
23
|
+
import { spawn as nodeSpawn } from 'node:child_process';
|
|
24
|
+
import { basename } from 'node:path';
|
|
25
|
+
import { awakeNow, createLogger } from '@bendyline/gezel';
|
|
26
|
+
import { listProcessSnapshots } from '@bendyline/gezel-client/node';
|
|
27
|
+
import { windowsHeadlessSpawnOptions } from '@bendyline/gezel/native';
|
|
28
|
+
import { acquireNativeCapacity, } from './device-capacity.js';
|
|
29
|
+
const log = createLogger('native');
|
|
30
|
+
/**
|
|
31
|
+
* Convert Electron's virtual `app.asar` paths to the real files staged by
|
|
32
|
+
* electron-builder under `app.asar.unpacked`. Electron patches `fs`, so an
|
|
33
|
+
* existence check in the main process can succeed through the virtual path,
|
|
34
|
+
* but the OS receives native spawn paths unchanged and rejects them with
|
|
35
|
+
* ENOTDIR. Normalize again at the service's final launch boundary so embedded
|
|
36
|
+
* mode is safe even when a pre-set env var bypassed the Electron resolver.
|
|
37
|
+
*/
|
|
38
|
+
export function normalizeNativeEngineLaunch(launch) {
|
|
39
|
+
const redirect = (value) => value.replace(/(^|[\\/])app\.asar(?=[\\/]|$)/g, '$1app.asar.unpacked');
|
|
40
|
+
return {
|
|
41
|
+
...launch,
|
|
42
|
+
command: redirect(launch.command),
|
|
43
|
+
args: launch.args.map(redirect),
|
|
44
|
+
...(launch.cwd ? { cwd: redirect(launch.cwd) } : {}),
|
|
45
|
+
...(launch.env
|
|
46
|
+
? {
|
|
47
|
+
env: Object.fromEntries(Object.entries(launch.env).map(([key, value]) => [key, redirect(value)])),
|
|
48
|
+
}
|
|
49
|
+
: {}),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const RESTART_BUDGET = 3;
|
|
53
|
+
const RESTART_WINDOW_MS = 60_000;
|
|
54
|
+
/**
|
|
55
|
+
* Ceiling on `recoverStartup` retries within one start. Two covers the
|
|
56
|
+
* full MoE degradation ladder (partial split → all experts to RAM →
|
|
57
|
+
* engine-owned fit); a hook that keeps asking past that is looping.
|
|
58
|
+
*/
|
|
59
|
+
const MAX_STARTUP_RECOVERIES = 2;
|
|
60
|
+
const HEALTH_FAIL_THRESHOLD = 3;
|
|
61
|
+
/**
|
|
62
|
+
* How often to restate that the accelerator is under pressure and this engine
|
|
63
|
+
* cannot do anything about it. Long, because on a card sized to one model the
|
|
64
|
+
* condition is ordinary and steady — the point is a trail, not an alarm.
|
|
65
|
+
*/
|
|
66
|
+
const PRESSURE_HELD_LOG_INTERVAL_MS = 5 * 60_000;
|
|
67
|
+
const RECENT_OUTPUT_MAX_BYTES = 64 * 1024;
|
|
68
|
+
const EXIT_ATTRIBUTION_WAIT_MS = 250;
|
|
69
|
+
/**
|
|
70
|
+
* After SIGKILLing reaped orphans, how long to wait for them to actually leave
|
|
71
|
+
* the process table before spawning the replacement. SIGKILL is asynchronous
|
|
72
|
+
* and a model-loaded engine holding tens of GB of wired GPU memory (ds4-server
|
|
73
|
+
* especially) takes a beat to die. ds4-server enforces a HARD singleton, so
|
|
74
|
+
* spawning into a still-dying orphan races straight into its "another ds4
|
|
75
|
+
* process is already running; refusing to start".
|
|
76
|
+
*/
|
|
77
|
+
const ORPHAN_REAP_WAIT_MS = 10_000;
|
|
78
|
+
/**
|
|
79
|
+
* Process-wide set of engine child PIDs currently owned by a LIVE
|
|
80
|
+
* supervisor in this daemon. The orphan reaper consults it so a freshly
|
|
81
|
+
* constructed supervisor never reaps a *sibling* engine that another
|
|
82
|
+
* live supervisor is actively driving.
|
|
83
|
+
*
|
|
84
|
+
* This is the fix for a real outage: a model switch (or any second
|
|
85
|
+
* engine) spins up a new supervisor whose once-per-instance orphan
|
|
86
|
+
* sweep matches every process sharing the engine's script path —
|
|
87
|
+
* including the engine still mid-agentic-turn for another session. The
|
|
88
|
+
* old reaper SIGKILL'd it, dropping the in-flight HTTP stream, which
|
|
89
|
+
* surfaced to the user as a bare "terminated". A live, owned engine is
|
|
90
|
+
* never an orphan: its supervisor manages its lifecycle (idle stop,
|
|
91
|
+
* eviction, health restart) and can drain a turn cleanly. The reaper is
|
|
92
|
+
* only for engines whose owner is *gone* (force-quit, OS-reaped
|
|
93
|
+
* Electron), which by definition aren't in this set.
|
|
94
|
+
*
|
|
95
|
+
* Shared module singleton: every supervisor in the process registers
|
|
96
|
+
* its child here on spawn and removes it on exit.
|
|
97
|
+
*/
|
|
98
|
+
const liveEnginePids = new Set();
|
|
99
|
+
/** Test-only: clear the live-engine registry between cases so a pid
|
|
100
|
+
* registered by one test can't suppress an orphan reap in the next. */
|
|
101
|
+
export function __resetLiveEnginePidsForTest() {
|
|
102
|
+
liveEnginePids.clear();
|
|
103
|
+
}
|
|
104
|
+
export class NativeEngineSupervisor {
|
|
105
|
+
capacity;
|
|
106
|
+
capacityLease;
|
|
107
|
+
capacityAbort;
|
|
108
|
+
startFlight;
|
|
109
|
+
activeRequests = 0;
|
|
110
|
+
stopFlight;
|
|
111
|
+
yieldFlight;
|
|
112
|
+
get coordinatesCapacity() {
|
|
113
|
+
return this.capacity !== undefined;
|
|
114
|
+
}
|
|
115
|
+
async withRequest(run, signal) {
|
|
116
|
+
await this.yieldForWaitingCapacity(signal);
|
|
117
|
+
signal?.throwIfAborted();
|
|
118
|
+
this.activeRequests++;
|
|
119
|
+
try {
|
|
120
|
+
return await run();
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
this.activeRequests--;
|
|
124
|
+
this.markUsed();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** Stop accepting fresh GPU work while an older memory claim needs this model to drain. */
|
|
128
|
+
async yieldForWaitingCapacity(signal) {
|
|
129
|
+
if (this.yieldFlight)
|
|
130
|
+
return this.yieldFlight;
|
|
131
|
+
if (!this.capacityLease)
|
|
132
|
+
return;
|
|
133
|
+
const flight = (async () => {
|
|
134
|
+
if (!(await this.capacityLease?.shouldYield()))
|
|
135
|
+
return;
|
|
136
|
+
const started = awakeNow();
|
|
137
|
+
while (this.engineBusy() && this.state.kind === 'running') {
|
|
138
|
+
signal?.throwIfAborted();
|
|
139
|
+
if (awakeNow() - started > 5 * 60_000)
|
|
140
|
+
throw new Error('Engine is busy serving requests while another model waits for memory.');
|
|
141
|
+
await sleep(100);
|
|
142
|
+
}
|
|
143
|
+
signal?.throwIfAborted();
|
|
144
|
+
if (this.state.kind !== 'running')
|
|
145
|
+
return;
|
|
146
|
+
this.onLog(`${this.logPrefix} releasing idle engine for a waiting memory request`);
|
|
147
|
+
await this.runFreeze();
|
|
148
|
+
await this.stop('memory-pressure');
|
|
149
|
+
})().finally(() => {
|
|
150
|
+
if (this.yieldFlight === flight)
|
|
151
|
+
this.yieldFlight = undefined;
|
|
152
|
+
});
|
|
153
|
+
this.yieldFlight = flight;
|
|
154
|
+
return flight;
|
|
155
|
+
}
|
|
156
|
+
state = { kind: 'stopped' };
|
|
157
|
+
resolveLaunch;
|
|
158
|
+
idleTimeoutMs;
|
|
159
|
+
freezeTimeoutMs;
|
|
160
|
+
onFreeze;
|
|
161
|
+
beforeStop;
|
|
162
|
+
beforeStopTimeoutMs;
|
|
163
|
+
isBusy;
|
|
164
|
+
memoryPressure;
|
|
165
|
+
pressureIdleTimeoutMs;
|
|
166
|
+
pressureCheckInFlight = false;
|
|
167
|
+
pressureDeadlineAt;
|
|
168
|
+
/** Awake ms at which the current unbroken stretch of pressure began. */
|
|
169
|
+
pressureHeldSince;
|
|
170
|
+
/** Awake ms of the last "still pressured, cannot act" log line. */
|
|
171
|
+
pressureHeldLoggedAt;
|
|
172
|
+
freezeTimer;
|
|
173
|
+
/** True once the current idle window has fired its Stage-1 freeze.
|
|
174
|
+
* Reset on next `markUsed` so subsequent idle windows re-fire. */
|
|
175
|
+
freezeFired = false;
|
|
176
|
+
healthIntervalMs;
|
|
177
|
+
startupTimeoutMs;
|
|
178
|
+
spawn;
|
|
179
|
+
fetchImpl;
|
|
180
|
+
onLog;
|
|
181
|
+
onExit;
|
|
182
|
+
recoverStartup;
|
|
183
|
+
onRawLine;
|
|
184
|
+
logPrefix;
|
|
185
|
+
readinessPath;
|
|
186
|
+
readyOnAnyResponse;
|
|
187
|
+
psRunner;
|
|
188
|
+
killProcess;
|
|
189
|
+
platform;
|
|
190
|
+
// Ad-hoc log listeners attached at runtime (vs. the construction-time
|
|
191
|
+
// `onLog` / `onRawLine` hooks). Used by the owning provider to
|
|
192
|
+
// subscribe to engine log lines for the duration of a single request
|
|
193
|
+
// — e.g. `StableDiffusionCppProvider.generate` parses sd-server's
|
|
194
|
+
// sampling progress from each line and republishes it as chat
|
|
195
|
+
// events. Listeners receive the SAME prefixed line (`[sd-server] …`)
|
|
196
|
+
// that `onLog` would; supervisor errors thrown from a listener are
|
|
197
|
+
// swallowed so a buggy parser can't take down the supervisor.
|
|
198
|
+
logListeners = new Set();
|
|
199
|
+
recentStarts = [];
|
|
200
|
+
lastUsedAt = 0;
|
|
201
|
+
/**
|
|
202
|
+
* The same mark on the awake clock. Every idle/pressure DECISION reads this
|
|
203
|
+
* one; {@link lastUsedAt} stays wall-clock because it is rendered to the
|
|
204
|
+
* user. A host suspension advances wall clock without giving the engine any
|
|
205
|
+
* time, and charging that against idleness unloaded resident models on every
|
|
206
|
+
* dark-wake cycle — the next turn then paid a full cold load.
|
|
207
|
+
*/
|
|
208
|
+
lastUsedAwakeAt = 0;
|
|
209
|
+
/** PID of the engine child this supervisor currently owns, registered
|
|
210
|
+
* in {@link liveEnginePids} so a sibling supervisor's orphan reaper
|
|
211
|
+
* leaves it alone. Set on spawn, cleared on exit. */
|
|
212
|
+
ownedPid;
|
|
213
|
+
// Tracks whether we've already done the once-per-process orphan
|
|
214
|
+
// sweep. Reaping runs ONLY before the first spawn — during restarts
|
|
215
|
+
// we'd risk killing our own freshly-launched child.
|
|
216
|
+
didOrphanSweep = false;
|
|
217
|
+
// When the owning provider detects a fatal error in the child's
|
|
218
|
+
// stdout (e.g. MLX's `ValueError: Received N parameters not in
|
|
219
|
+
// model`), it calls `abortStartup(err)` to short-circuit the
|
|
220
|
+
// `waitForReady` poll loop — otherwise the supervisor would keep
|
|
221
|
+
// pinging `/health` for the full startup-timeout window even
|
|
222
|
+
// though the worker thread has already died. Cleared on each
|
|
223
|
+
// fresh start.
|
|
224
|
+
startupAbort = null;
|
|
225
|
+
idleTimer;
|
|
226
|
+
lastStartFailureAt;
|
|
227
|
+
lastStartFailure;
|
|
228
|
+
healthTimer;
|
|
229
|
+
currentStartedAt = 0;
|
|
230
|
+
currentPid;
|
|
231
|
+
/** Sticky for the current child; remains true while an expected stop moves
|
|
232
|
+
* state to `stopped` before the OS exit event arrives. */
|
|
233
|
+
currentReachedReady = false;
|
|
234
|
+
currentDiagnostics;
|
|
235
|
+
currentOutput = '';
|
|
236
|
+
currentPanic;
|
|
237
|
+
expectedExitReason;
|
|
238
|
+
lastExit;
|
|
239
|
+
exitListeners = new Set();
|
|
240
|
+
constructor(opts) {
|
|
241
|
+
this.capacity = opts.capacity;
|
|
242
|
+
this.resolveLaunch = opts.resolveLaunch;
|
|
243
|
+
// 30 min by default — aligns with `OLLAMA_TURN_TIMEOUT_MS` in chat/manager.ts,
|
|
244
|
+
// the accepted ceiling for a single local-engine turn. The previous 10-min
|
|
245
|
+
// default killed llama-server mid-stream on legitimately-long Builder
|
|
246
|
+
// generations (tankcombat trial: 1.7 MB streamed at T+10:27 →
|
|
247
|
+
// VRAM-saver fired → empty assistant message). Callers that want faster
|
|
248
|
+
// VRAM reclaim (sd-server idle policy, tests) pass an explicit value.
|
|
249
|
+
this.idleTimeoutMs = opts.idleTimeoutMs ?? 30 * 60 * 1000;
|
|
250
|
+
// Default freeze = half of idle when both are set. Operators get
|
|
251
|
+
// the staged behavior automatically without further config; legacy
|
|
252
|
+
// callers that pass explicit idleTimeoutMs and don't want a freeze
|
|
253
|
+
// can pass freezeTimeoutMs:0 to disable it explicitly.
|
|
254
|
+
if (opts.freezeTimeoutMs !== undefined) {
|
|
255
|
+
this.freezeTimeoutMs = opts.freezeTimeoutMs;
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
this.freezeTimeoutMs = 0; // disabled by default
|
|
259
|
+
}
|
|
260
|
+
if (opts.onFreeze)
|
|
261
|
+
this.onFreeze = opts.onFreeze;
|
|
262
|
+
if (opts.beforeStop)
|
|
263
|
+
this.beforeStop = opts.beforeStop;
|
|
264
|
+
this.beforeStopTimeoutMs = opts.beforeStopTimeoutMs ?? 30_000;
|
|
265
|
+
if (opts.isBusy)
|
|
266
|
+
this.isBusy = opts.isBusy;
|
|
267
|
+
if (opts.memoryPressure)
|
|
268
|
+
this.memoryPressure = opts.memoryPressure;
|
|
269
|
+
this.pressureIdleTimeoutMs = opts.pressureIdleTimeoutMs ?? 60_000;
|
|
270
|
+
this.healthIntervalMs = opts.healthIntervalMs ?? 15_000;
|
|
271
|
+
this.startupTimeoutMs = opts.startupTimeoutMs ?? 60_000;
|
|
272
|
+
this.spawn = opts.spawn ?? nodeSpawn;
|
|
273
|
+
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
274
|
+
this.onLog = opts.onLog ?? ((line) => log.info(line));
|
|
275
|
+
if (opts.onExit)
|
|
276
|
+
this.onExit = opts.onExit;
|
|
277
|
+
if (opts.recoverStartup)
|
|
278
|
+
this.recoverStartup = opts.recoverStartup;
|
|
279
|
+
if (opts.onRawLine)
|
|
280
|
+
this.onRawLine = opts.onRawLine;
|
|
281
|
+
this.logPrefix = opts.logPrefix ?? '[native]';
|
|
282
|
+
this.readinessPath = opts.readinessPath ?? '/health';
|
|
283
|
+
this.readyOnAnyResponse = opts.readyOnAnyResponse ?? false;
|
|
284
|
+
this.platform = opts.platform ?? process.platform;
|
|
285
|
+
this.psRunner = opts.psRunner ?? (() => defaultPsRunner(this.platform));
|
|
286
|
+
this.killProcess = opts.killProcess ?? ((pid, sig) => process.kill(pid, sig));
|
|
287
|
+
}
|
|
288
|
+
/** URL the provider should hit. Only meaningful while running. */
|
|
289
|
+
currentBaseUrl() {
|
|
290
|
+
if (this.state.kind === 'running' || this.state.kind === 'starting') {
|
|
291
|
+
return this.state.launch.baseUrl;
|
|
292
|
+
}
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* PID of the supervised child when running. Used by providers that
|
|
297
|
+
* want to poll the child's resource usage (e.g. resident-set size
|
|
298
|
+
* for an "engine memory" telemetry event) — the supervisor owns
|
|
299
|
+
* the lifetime of the pid, so consumers don't have to track
|
|
300
|
+
* restarts themselves.
|
|
301
|
+
*/
|
|
302
|
+
currentChildPid() {
|
|
303
|
+
if (this.state.kind === 'running' || this.state.kind === 'starting') {
|
|
304
|
+
return this.state.child.pid ?? undefined;
|
|
305
|
+
}
|
|
306
|
+
return undefined;
|
|
307
|
+
}
|
|
308
|
+
/** Stable status shape for the engine pool and once-a-second memory UI. */
|
|
309
|
+
lifecycleSnapshot() {
|
|
310
|
+
const running = this.state.kind === 'running' || this.state.kind === 'starting';
|
|
311
|
+
const active = running && this.engineBusy();
|
|
312
|
+
const idleDeadline = running && !active && this.lastUsedAwakeAt > 0 && this.idleTimeoutMs > 0
|
|
313
|
+
? this.wallClockFor(this.lastUsedAwakeAt + this.idleTimeoutMs)
|
|
314
|
+
: null;
|
|
315
|
+
const pressureDeadline = running && !active && this.pressureDeadlineAt !== undefined
|
|
316
|
+
? this.wallClockFor(this.pressureDeadlineAt)
|
|
317
|
+
: null;
|
|
318
|
+
const usePressure = pressureDeadline !== null && (idleDeadline === null || pressureDeadline < idleDeadline);
|
|
319
|
+
const pid = this.currentChildPid();
|
|
320
|
+
return {
|
|
321
|
+
running,
|
|
322
|
+
active,
|
|
323
|
+
...(pid !== undefined ? { pid } : {}),
|
|
324
|
+
lastUsedAt: this.lastUsedAt > 0 ? this.lastUsedAt : null,
|
|
325
|
+
unloadAt: usePressure ? pressureDeadline : idleDeadline,
|
|
326
|
+
idleTimeoutMs: this.idleTimeoutMs,
|
|
327
|
+
releaseReason: usePressure ? 'memory-pressure' : idleDeadline !== null ? 'idle' : null,
|
|
328
|
+
...(this.lastStartFailureAt !== undefined
|
|
329
|
+
? {
|
|
330
|
+
startFailedAt: this.lastStartFailureAt,
|
|
331
|
+
...(this.lastStartFailure ? { startFailure: this.lastStartFailure } : {}),
|
|
332
|
+
}
|
|
333
|
+
: {}),
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
/** Most recently sealed child exit, including expected idle/shutdown exits. */
|
|
337
|
+
lastExitSnapshot() {
|
|
338
|
+
return this.lastExit
|
|
339
|
+
? {
|
|
340
|
+
...this.lastExit,
|
|
341
|
+
...(this.lastExit.diagnostics ? { diagnostics: { ...this.lastExit.diagnostics } } : {}),
|
|
342
|
+
}
|
|
343
|
+
: undefined;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Launch provenance of the LIVE child: pid, start time, and the
|
|
347
|
+
* request-independent launch facts the spawner attached (granted context
|
|
348
|
+
* window, slots, KV dtype, backend — the payload behind the
|
|
349
|
+
* `launch {...}` log line). Undefined when no child is up, so callers
|
|
350
|
+
* (Settings → About, `/api/system/diagnostics`) never show a stale grant
|
|
351
|
+
* for an engine that already exited.
|
|
352
|
+
*/
|
|
353
|
+
launchSnapshot() {
|
|
354
|
+
if (this.state.kind !== 'running' && this.state.kind !== 'starting')
|
|
355
|
+
return undefined;
|
|
356
|
+
const pid = this.currentChildPid();
|
|
357
|
+
return {
|
|
358
|
+
...(pid !== undefined ? { pid } : {}),
|
|
359
|
+
startedAt: this.currentStartedAt,
|
|
360
|
+
...(this.currentDiagnostics ? { diagnostics: { ...this.currentDiagnostics } } : {}),
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Wait briefly for an unexpected exit attributable to a request that began
|
|
365
|
+
* at `sinceMs`. Fetch/stream teardown can race Node's child `exit` event by
|
|
366
|
+
* a few milliseconds; this bounded wait prevents CUDA SIGABRTs from being
|
|
367
|
+
* flattened into a generic undici "terminated" error.
|
|
368
|
+
*/
|
|
369
|
+
async waitForUnexpectedExitSince(sinceMs, timeoutMs = EXIT_ATTRIBUTION_WAIT_MS) {
|
|
370
|
+
const matches = (snapshot) => !snapshot.expected && snapshot.exitedAt >= sinceMs;
|
|
371
|
+
if (this.lastExit && matches(this.lastExit))
|
|
372
|
+
return { ...this.lastExit };
|
|
373
|
+
if (timeoutMs <= 0)
|
|
374
|
+
return undefined;
|
|
375
|
+
return await new Promise((resolve) => {
|
|
376
|
+
const listener = (snapshot) => {
|
|
377
|
+
if (!matches(snapshot))
|
|
378
|
+
return;
|
|
379
|
+
clearTimeout(timer);
|
|
380
|
+
this.exitListeners.delete(listener);
|
|
381
|
+
resolve({ ...snapshot });
|
|
382
|
+
};
|
|
383
|
+
this.exitListeners.add(listener);
|
|
384
|
+
const timer = setTimeout(() => {
|
|
385
|
+
this.exitListeners.delete(listener);
|
|
386
|
+
resolve(undefined);
|
|
387
|
+
}, timeoutMs);
|
|
388
|
+
timer.unref?.();
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Ensure the engine is running and reachable. If it's stopped, spawn
|
|
393
|
+
* it; if it's starting, await the in-flight readiness probe; if it's
|
|
394
|
+
* already running, no-op. Rejects if the restart budget is exhausted.
|
|
395
|
+
*/
|
|
396
|
+
async ensureRunning() {
|
|
397
|
+
if (this.stopFlight)
|
|
398
|
+
await this.stopFlight;
|
|
399
|
+
this.lastUsedAt = Date.now();
|
|
400
|
+
this.lastUsedAwakeAt = awakeNow();
|
|
401
|
+
this.pressureDeadlineAt = undefined;
|
|
402
|
+
if (this.startFlight)
|
|
403
|
+
await this.startFlight;
|
|
404
|
+
if (this.state.kind === 'running') {
|
|
405
|
+
this.resetIdleTimer();
|
|
406
|
+
return this.state.launch;
|
|
407
|
+
}
|
|
408
|
+
if (this.state.kind === 'starting') {
|
|
409
|
+
await this.state.readyPromise;
|
|
410
|
+
const current = this.state;
|
|
411
|
+
if (current.kind === 'running')
|
|
412
|
+
return current.launch;
|
|
413
|
+
throw new Error(`${this.logPrefix} failed to start`);
|
|
414
|
+
}
|
|
415
|
+
if (this.state.kind === 'restart-budget-exhausted') {
|
|
416
|
+
if (Date.now() - this.state.at < RESTART_WINDOW_MS) {
|
|
417
|
+
throw new Error(`${this.logPrefix} failed repeatedly. Wait a minute and try again, or check logs in Settings.`);
|
|
418
|
+
}
|
|
419
|
+
this.recentStarts = [];
|
|
420
|
+
this.state = { kind: 'stopped' };
|
|
421
|
+
}
|
|
422
|
+
await this.startFresh();
|
|
423
|
+
const current = this.state;
|
|
424
|
+
if (current.kind === 'running')
|
|
425
|
+
return current.launch;
|
|
426
|
+
throw new Error(`${this.logPrefix} did not reach running state`);
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Attach a transient log listener that receives every prefixed
|
|
430
|
+
* stdout/stderr line (`[sd-server] …`) for the duration of the
|
|
431
|
+
* returned unsubscribe call. Used by the owning provider to parse
|
|
432
|
+
* engine progress for a single in-flight request — e.g. sd-server's
|
|
433
|
+
* `|==> | 3/20 - 18.20s/it` sampling lines — and translate them
|
|
434
|
+
* into structured progress events for the UI.
|
|
435
|
+
*
|
|
436
|
+
* Listeners are independent of `onLog` / `onRawLine` (which are
|
|
437
|
+
* construction-time hooks for log routing + classification). Adding
|
|
438
|
+
* one does not suppress either.
|
|
439
|
+
*/
|
|
440
|
+
subscribeLogLines(listener) {
|
|
441
|
+
this.logListeners.add(listener);
|
|
442
|
+
return () => {
|
|
443
|
+
this.logListeners.delete(listener);
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
/** Update the last-used clock without triggering a start. */
|
|
447
|
+
markUsed() {
|
|
448
|
+
this.lastUsedAt = Date.now();
|
|
449
|
+
this.lastUsedAwakeAt = awakeNow();
|
|
450
|
+
this.pressureDeadlineAt = undefined;
|
|
451
|
+
this.resetIdleTimer();
|
|
452
|
+
if (this.capacityLease)
|
|
453
|
+
void this.pressureProbe();
|
|
454
|
+
}
|
|
455
|
+
async stop(reason = 'stop') {
|
|
456
|
+
if (this.stopFlight)
|
|
457
|
+
return this.stopFlight;
|
|
458
|
+
const flight = this.stopChild(reason).finally(() => {
|
|
459
|
+
if (this.stopFlight === flight)
|
|
460
|
+
this.stopFlight = undefined;
|
|
461
|
+
});
|
|
462
|
+
this.stopFlight = flight;
|
|
463
|
+
return flight;
|
|
464
|
+
}
|
|
465
|
+
async stopChild(reason) {
|
|
466
|
+
this.capacityAbort?.abort(new Error(`${this.logPrefix} engine start cancelled`));
|
|
467
|
+
this.startupAbort?.abort(new Error(`${this.logPrefix} engine start cancelled`));
|
|
468
|
+
await this.startFlight?.catch(() => { });
|
|
469
|
+
this.clearIdleTimer();
|
|
470
|
+
this.clearFreezeTimer();
|
|
471
|
+
this.clearHealthTimer();
|
|
472
|
+
if (this.state.kind === 'stopped' || this.state.kind === 'restart-budget-exhausted')
|
|
473
|
+
return;
|
|
474
|
+
const { child } = this.state;
|
|
475
|
+
if (this.state.kind === 'running' && reason !== 'health-restart') {
|
|
476
|
+
await this.runBeforeStop(reason, this.state.launch.baseUrl);
|
|
477
|
+
}
|
|
478
|
+
this.pressureDeadlineAt = undefined;
|
|
479
|
+
this.expectedExitReason = reason;
|
|
480
|
+
this.state = { kind: 'stopped' };
|
|
481
|
+
await killGracefully(child);
|
|
482
|
+
}
|
|
483
|
+
async runBeforeStop(reason, baseUrl) {
|
|
484
|
+
if (!this.beforeStop)
|
|
485
|
+
return;
|
|
486
|
+
const started = Date.now();
|
|
487
|
+
let timer;
|
|
488
|
+
try {
|
|
489
|
+
await Promise.race([
|
|
490
|
+
Promise.resolve(this.beforeStop({ reason, baseUrl })),
|
|
491
|
+
new Promise((resolve) => {
|
|
492
|
+
timer = setTimeout(() => {
|
|
493
|
+
this.onLog(`${this.logPrefix} pre-stop hook still running after ${this.beforeStopTimeoutMs}ms — stopping anyway`);
|
|
494
|
+
resolve();
|
|
495
|
+
}, this.beforeStopTimeoutMs);
|
|
496
|
+
timer.unref?.();
|
|
497
|
+
}),
|
|
498
|
+
]);
|
|
499
|
+
this.onLog(`${this.logPrefix} pre-stop hook done in ${Date.now() - started}ms (${reason})`);
|
|
500
|
+
}
|
|
501
|
+
catch (err) {
|
|
502
|
+
this.onLog(`${this.logPrefix} pre-stop hook failed (${reason}): ${err instanceof Error ? err.message : String(err)}`);
|
|
503
|
+
}
|
|
504
|
+
finally {
|
|
505
|
+
if (timer)
|
|
506
|
+
clearTimeout(timer);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
async startFresh() {
|
|
510
|
+
if (this.startFlight)
|
|
511
|
+
return this.startFlight;
|
|
512
|
+
this.capacityAbort = new AbortController();
|
|
513
|
+
const flight = this.startFreshAttempt()
|
|
514
|
+
.then((result) => {
|
|
515
|
+
this.lastStartFailureAt = undefined;
|
|
516
|
+
this.lastStartFailure = undefined;
|
|
517
|
+
return result;
|
|
518
|
+
})
|
|
519
|
+
.catch((err) => {
|
|
520
|
+
this.lastStartFailureAt = Date.now();
|
|
521
|
+
this.lastStartFailure = err instanceof Error ? err.message : String(err);
|
|
522
|
+
throw err;
|
|
523
|
+
})
|
|
524
|
+
.finally(() => {
|
|
525
|
+
if (this.startFlight === flight)
|
|
526
|
+
this.startFlight = undefined;
|
|
527
|
+
this.capacityAbort = undefined;
|
|
528
|
+
});
|
|
529
|
+
this.startFlight = flight;
|
|
530
|
+
return flight;
|
|
531
|
+
}
|
|
532
|
+
async startFreshAttempt(isRetry = false, recoveryAttempt = 0) {
|
|
533
|
+
const now = Date.now();
|
|
534
|
+
this.recentStarts = this.recentStarts.filter((t) => now - t < RESTART_WINDOW_MS);
|
|
535
|
+
if (this.recentStarts.length >= RESTART_BUDGET) {
|
|
536
|
+
this.state = { kind: 'restart-budget-exhausted', at: now };
|
|
537
|
+
throw new Error(`${this.logPrefix} crashed too many times; stopping restart attempts for a minute`);
|
|
538
|
+
}
|
|
539
|
+
const launch = normalizeNativeEngineLaunch(await this.resolveLaunch());
|
|
540
|
+
// First spawn of this process? Sweep up any matching orphans from
|
|
541
|
+
// previous app launches that didn't get a chance to clean up
|
|
542
|
+
// (force-quit, OS reaped Electron, before-quit handler crashed,
|
|
543
|
+
// etc.). Match is anchored on the binary path so we don't touch
|
|
544
|
+
// unrelated processes; restarts skip the sweep so we don't shoot
|
|
545
|
+
// our own foot.
|
|
546
|
+
if (!this.didOrphanSweep) {
|
|
547
|
+
this.didOrphanSweep = true;
|
|
548
|
+
await this.reapOrphansMatching(launch).catch((err) => {
|
|
549
|
+
this.onLog(`${this.logPrefix} orphan sweep failed (continuing): ${err instanceof Error ? err.message : err}`);
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
this.capacityAbort?.signal.throwIfAborted();
|
|
553
|
+
if (this.capacity) {
|
|
554
|
+
this.capacityLease = await acquireNativeCapacity(this.capacity, launch, this.capacityAbort.signal, (message) => {
|
|
555
|
+
const line = `${this.logPrefix} memory-admission: ${message}`;
|
|
556
|
+
this.onLog(line);
|
|
557
|
+
try {
|
|
558
|
+
this.onRawLine?.(line);
|
|
559
|
+
}
|
|
560
|
+
catch {
|
|
561
|
+
/* progress cannot abort admission */
|
|
562
|
+
}
|
|
563
|
+
for (const listener of this.logListeners) {
|
|
564
|
+
try {
|
|
565
|
+
listener(line);
|
|
566
|
+
}
|
|
567
|
+
catch {
|
|
568
|
+
/* progress cannot abort admission */
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
this.recentStarts.push(Date.now());
|
|
574
|
+
let child;
|
|
575
|
+
try {
|
|
576
|
+
child = this.spawn(launch.command, launch.args, {
|
|
577
|
+
env: { ...process.env, ...(launch.env ?? {}) },
|
|
578
|
+
...(launch.cwd ? { cwd: launch.cwd } : {}),
|
|
579
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
580
|
+
// The supervisor owns this engine for its entire lifetime. Keep the
|
|
581
|
+
// Windows console hidden without detaching the process from its
|
|
582
|
+
// owner; detachment is what surfaces a new terminal window.
|
|
583
|
+
...windowsHeadlessSpawnOptions(),
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
catch (err) {
|
|
587
|
+
await this.releaseCapacity();
|
|
588
|
+
throw nativeSpawnError(this.logPrefix, launch, err);
|
|
589
|
+
}
|
|
590
|
+
// Register the child as a live, owned engine so no sibling
|
|
591
|
+
// supervisor's orphan reaper can SIGKILL it mid-turn. Cleared in
|
|
592
|
+
// handleExit. `pid` is undefined only if the spawn itself failed —
|
|
593
|
+
// handleExit will fire and tear the state down regardless.
|
|
594
|
+
this.ownedPid = typeof child.pid === 'number' ? child.pid : undefined;
|
|
595
|
+
if (this.ownedPid !== undefined)
|
|
596
|
+
liveEnginePids.add(this.ownedPid);
|
|
597
|
+
this.currentStartedAt = Date.now();
|
|
598
|
+
this.currentPid = typeof child.pid === 'number' ? child.pid : undefined;
|
|
599
|
+
this.currentReachedReady = false;
|
|
600
|
+
this.currentDiagnostics = launch.diagnostics ? { ...launch.diagnostics } : undefined;
|
|
601
|
+
this.currentOutput = '';
|
|
602
|
+
this.currentPanic = undefined;
|
|
603
|
+
this.expectedExitReason = undefined;
|
|
604
|
+
this.onLog(`${this.logPrefix} launch ${JSON.stringify({
|
|
605
|
+
pid: this.currentPid ?? null,
|
|
606
|
+
command: basename(launch.command),
|
|
607
|
+
...(this.currentDiagnostics ?? {}),
|
|
608
|
+
})}`);
|
|
609
|
+
const emitLine = (line) => {
|
|
610
|
+
if (!line)
|
|
611
|
+
return;
|
|
612
|
+
const prefixed = `${this.logPrefix} ${line}`;
|
|
613
|
+
this.appendRecentOutput(prefixed);
|
|
614
|
+
this.onLog(prefixed);
|
|
615
|
+
if (this.onRawLine) {
|
|
616
|
+
try {
|
|
617
|
+
this.onRawLine(prefixed);
|
|
618
|
+
}
|
|
619
|
+
catch (err) {
|
|
620
|
+
// Never let a bad classifier take down the supervisor.
|
|
621
|
+
log.warn(`${this.logPrefix} onRawLine threw: ${err instanceof Error ? err.message : err}`);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
if (this.logListeners.size > 0) {
|
|
625
|
+
for (const listener of this.logListeners) {
|
|
626
|
+
try {
|
|
627
|
+
listener(prefixed);
|
|
628
|
+
}
|
|
629
|
+
catch (err) {
|
|
630
|
+
log.warn(`${this.logPrefix} log listener threw: ${err instanceof Error ? err.message : err}`);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
const chunkReader = () => {
|
|
636
|
+
let pending = '';
|
|
637
|
+
const onChunk = (buf) => {
|
|
638
|
+
const text = pending + buf.toString();
|
|
639
|
+
// Split on CR *and* LF so the optional line classifier sees one
|
|
640
|
+
// logical record at a time. Most engines emit line-buffered
|
|
641
|
+
// output (\n), but tqdm-style progress bars — notably mlx's
|
|
642
|
+
// chunked-prefill `Prefill: NN%|…` meter — repaint a single line
|
|
643
|
+
// in place with a bare `\r` and emit no `\n` until the bar hits
|
|
644
|
+
// 100%. Splitting on `\n` alone buffers the entire prefill arc
|
|
645
|
+
// into one unterminated record that only flushes at completion,
|
|
646
|
+
// so the classifier never sees per-step progress and the mlx
|
|
647
|
+
// idle watchdog kills the turn mid-prefill (it never gets the
|
|
648
|
+
// progress events that reset its per-chunk budget). Treating each
|
|
649
|
+
// `\r` repaint as its own line lets progress flow. A partial
|
|
650
|
+
// final line is retained across chunks and flushed on process exit.
|
|
651
|
+
const lines = text.split(/\r\n?|\n/);
|
|
652
|
+
pending = lines.pop() ?? '';
|
|
653
|
+
for (const line of lines)
|
|
654
|
+
emitLine(line);
|
|
655
|
+
};
|
|
656
|
+
return {
|
|
657
|
+
onChunk,
|
|
658
|
+
flush: () => {
|
|
659
|
+
if (pending)
|
|
660
|
+
emitLine(pending);
|
|
661
|
+
pending = '';
|
|
662
|
+
},
|
|
663
|
+
};
|
|
664
|
+
};
|
|
665
|
+
const stdout = chunkReader();
|
|
666
|
+
const stderr = chunkReader();
|
|
667
|
+
this.startupAbort = new AbortController();
|
|
668
|
+
let spawnFailed = false;
|
|
669
|
+
child.stdout?.on('data', stdout.onChunk);
|
|
670
|
+
child.stderr?.on('data', stderr.onChunk);
|
|
671
|
+
child.on('exit', (code, signal) => {
|
|
672
|
+
stdout.flush();
|
|
673
|
+
stderr.flush();
|
|
674
|
+
this.handleExit(code, signal);
|
|
675
|
+
});
|
|
676
|
+
// `child_process.spawn` reports missing/non-traversable executables through
|
|
677
|
+
// the child's `error` event on most Node/platform combinations. Without a
|
|
678
|
+
// listener this can escape as an opaque process-level exception; abort the
|
|
679
|
+
// readiness wait with the same actionable detail as a synchronous throw.
|
|
680
|
+
child.once('error', (err) => {
|
|
681
|
+
spawnFailed = true;
|
|
682
|
+
this.startupAbort?.abort(nativeSpawnError(this.logPrefix, launch, err));
|
|
683
|
+
});
|
|
684
|
+
const readyPromise = this.waitForReady(launch, this.startupAbort.signal);
|
|
685
|
+
this.state = { kind: 'starting', launch, child, readyPromise };
|
|
686
|
+
try {
|
|
687
|
+
const lease = this.capacityLease;
|
|
688
|
+
await Promise.all([
|
|
689
|
+
readyPromise,
|
|
690
|
+
child.pid !== undefined ? lease?.bind(child.pid) : Promise.resolve(),
|
|
691
|
+
]);
|
|
692
|
+
await lease?.ready();
|
|
693
|
+
this.startupAbort?.signal.throwIfAborted();
|
|
694
|
+
this.capacityAbort?.signal.throwIfAborted();
|
|
695
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
696
|
+
throw new Error(`${this.logPrefix} child exited before becoming ready`);
|
|
697
|
+
this.currentReachedReady = true;
|
|
698
|
+
this.state = { kind: 'running', launch, child, healthFails: 0 };
|
|
699
|
+
this.resetIdleTimer();
|
|
700
|
+
this.startHealthWatch();
|
|
701
|
+
}
|
|
702
|
+
catch (err) {
|
|
703
|
+
// The panic classifier state is cleared by handleExit — read it now,
|
|
704
|
+
// before killGracefully drives the exit, so the recovery hook below
|
|
705
|
+
// sees the fatal line however the failure was surfaced (stdout abort
|
|
706
|
+
// vs. child death vs. readiness timeout). Cast because TS narrows the
|
|
707
|
+
// field to `undefined` from the pre-spawn reset above and cannot see
|
|
708
|
+
// the stdout-callback writes in between.
|
|
709
|
+
const panicAtFailure = this.currentPanic;
|
|
710
|
+
const attemptStartedAt = this.currentStartedAt;
|
|
711
|
+
// A child that failed before the OS assigned a pid cannot be signalled
|
|
712
|
+
// and may never emit `exit`; waiting for killGracefully would wedge the
|
|
713
|
+
// chat turn while reporting the original spawn failure.
|
|
714
|
+
if (!spawnFailed) {
|
|
715
|
+
this.expectedExitReason ??= 'startup-abort';
|
|
716
|
+
await killGracefully(child);
|
|
717
|
+
}
|
|
718
|
+
await this.releaseCapacity();
|
|
719
|
+
this.state = { kind: 'stopped' };
|
|
720
|
+
// Singleton-conflict recovery: a hard-singleton engine (ds4-server)
|
|
721
|
+
// refuses to start while an owner-less orphan from a prior launch still
|
|
722
|
+
// holds the lock, surfaced as "child exited ... before becoming ready".
|
|
723
|
+
// On the first such failure, sweep orphans again — the first-spawn sweep
|
|
724
|
+
// may have raced one, or this is a RESTART (which skips the sweep) and the
|
|
725
|
+
// engine's own prior child lingered. Only retry if the sweep actually
|
|
726
|
+
// cleared a blocking process; a failure with nothing to clear is a real
|
|
727
|
+
// startup error (bad model, OOM), not a lock conflict, so don't burn a
|
|
728
|
+
// retry on it. `isRetry` caps recovery at a single extra attempt.
|
|
729
|
+
if (!isRetry && err instanceof Error && err.message.includes('before becoming ready')) {
|
|
730
|
+
const cleared = await this.reapOrphansMatching(launch).catch(() => 0);
|
|
731
|
+
if (cleared > 0) {
|
|
732
|
+
this.onLog(`${this.logPrefix} cleared ${cleared} blocking orphan(s) after a failed start — retrying once`);
|
|
733
|
+
this.startupAbort = null;
|
|
734
|
+
return await this.startFreshAttempt(true, recoveryAttempt);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
// Owner-driven recovery: give the launch resolver a chance to degrade
|
|
738
|
+
// its plan (the MoE offload ladder after a GPU OOM) and try again.
|
|
739
|
+
// Spawn failures are excluded — a missing binary cannot be planned
|
|
740
|
+
// around, and the child never produced a diagnosable panic.
|
|
741
|
+
if (!spawnFailed && this.recoverStartup && recoveryAttempt < MAX_STARTUP_RECOVERIES) {
|
|
742
|
+
const exit = this.lastExit;
|
|
743
|
+
const panicKind = panicAtFailure?.kind ??
|
|
744
|
+
(exit && exit.exitedAt >= attemptStartedAt ? exit.panicKind : undefined);
|
|
745
|
+
let retry = false;
|
|
746
|
+
try {
|
|
747
|
+
retry = await this.recoverStartup({ panicKind, attempt: recoveryAttempt });
|
|
748
|
+
}
|
|
749
|
+
catch (hookErr) {
|
|
750
|
+
this.onLog(`${this.logPrefix} recoverStartup hook threw (not retrying): ${hookErr instanceof Error ? hookErr.message : hookErr}`);
|
|
751
|
+
}
|
|
752
|
+
if (retry) {
|
|
753
|
+
this.onLog(`${this.logPrefix} start failed (${panicKind ?? 'no panic classified'}) — owner degraded the launch plan, retrying (${recoveryAttempt + 1}/${MAX_STARTUP_RECOVERIES})`);
|
|
754
|
+
this.startupAbort = null;
|
|
755
|
+
return await this.startFreshAttempt(isRetry, recoveryAttempt + 1);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
throw err;
|
|
759
|
+
}
|
|
760
|
+
finally {
|
|
761
|
+
this.startupAbort = null;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Abort an in-flight `startFresh` with a concrete error. Called by
|
|
766
|
+
* the owning provider when the child's stdout reveals a fatal
|
|
767
|
+
* condition (model-arch mismatch, OOM, missing dependency) that
|
|
768
|
+
* won't resolve by waiting longer — without this, the `waitForReady`
|
|
769
|
+
* poll would keep hitting `/health` for the full startup-timeout
|
|
770
|
+
* window even though the worker thread has already died.
|
|
771
|
+
*/
|
|
772
|
+
abortStartup(err) {
|
|
773
|
+
const ac = this.startupAbort;
|
|
774
|
+
if (!ac)
|
|
775
|
+
return;
|
|
776
|
+
ac.abort(err);
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Find and SIGKILL any owner-less engine process from a prior gezel
|
|
780
|
+
* launch that matches this engine. Best-effort: any error during the
|
|
781
|
+
* sweep is logged and swallowed so the spawn-on-startup path never
|
|
782
|
+
* aborts because of cleanup.
|
|
783
|
+
*
|
|
784
|
+
* Crucially, a process whose parent is still alive is NOT an orphan and
|
|
785
|
+
* is skipped. The process-local {@link liveEnginePids} registry protects
|
|
786
|
+
* sibling supervisors inside this daemon; the PPID check protects engines
|
|
787
|
+
* owned by a different live gezeld process/home. This matters for two
|
|
788
|
+
* concurrent isolated eval daemons: each uses the same engine binary, so a
|
|
789
|
+
* command-path-only sweep would otherwise SIGKILL the other's in-flight
|
|
790
|
+
* child and surface a bare `terminated` fetch error.
|
|
791
|
+
*
|
|
792
|
+
* Match strategy — anchor on every install-specific absolute path we
|
|
793
|
+
* can find in the launch spec, then check whether each `ps` row
|
|
794
|
+
* contains any of them:
|
|
795
|
+
*
|
|
796
|
+
* 1. `launch.command` — works directly when the binary is
|
|
797
|
+
* install-specific (llama-server, sd-server: a path under
|
|
798
|
+
* `~/.gezel/native/bin/`).
|
|
799
|
+
* 2. Any absolute-path argument that looks like a script or
|
|
800
|
+
* executable (`*.py`, `*.js`, etc.). For Python engines this is
|
|
801
|
+
* the case that *actually* fires: `launch.command` is the venv's
|
|
802
|
+
* `bin/python`, but that's typically a symlink chain to the
|
|
803
|
+
* system framework Python (e.g. `/Library/Frameworks/Python.../
|
|
804
|
+
* MacOS/Python`) — and macOS `ps` reports the resolved path, so
|
|
805
|
+
* the binary-path match never hits. The script path
|
|
806
|
+
* (`gezel_mlx_server.py` under the dist folder) appears verbatim
|
|
807
|
+
* in the ps command line and is unique to this install.
|
|
808
|
+
*
|
|
809
|
+
* `didOrphanSweep` gates this to one execution per supervisor instance
|
|
810
|
+
* (so we don't re-sweep on our own restarts). A real orphan is reparented
|
|
811
|
+
* to launchd/systemd (PPID 1) after its daemon exits. Any matching child
|
|
812
|
+
* with PPID > 1 still has a live owner and is conservatively left alone.
|
|
813
|
+
* Injected legacy `psRunner` rows may omit PPID and retain the historical
|
|
814
|
+
* match behavior; the production runner always includes it.
|
|
815
|
+
*
|
|
816
|
+
* macOS + Linux use `ps -axo pid=,ppid=,command=`. Windows uses the
|
|
817
|
+
* Win32_Process CIM snapshot; because Windows retains a dead creator's pid
|
|
818
|
+
* instead of reparenting to pid 1, absence of that ppid proves orphanhood.
|
|
819
|
+
*/
|
|
820
|
+
async reapOrphansMatching(launch) {
|
|
821
|
+
const anchors = this.buildOrphanAnchors(launch);
|
|
822
|
+
if (anchors.command.length === 0 && anchors.args.length === 0)
|
|
823
|
+
return 0;
|
|
824
|
+
const procs = await this.psRunner();
|
|
825
|
+
const livePids = new Set(procs.map(({ pid }) => pid));
|
|
826
|
+
const normalizedCommandAnchors = anchors.command.map((anchor) => normalizeProcessMatch(anchor, this.platform));
|
|
827
|
+
const normalizedArgAnchors = anchors.args.map((anchor) => normalizeProcessMatch(anchor, this.platform));
|
|
828
|
+
const ourPid = process.pid;
|
|
829
|
+
const targets = [];
|
|
830
|
+
for (const { pid, ppid, command } of procs) {
|
|
831
|
+
if (pid === ourPid)
|
|
832
|
+
continue;
|
|
833
|
+
// Never reap an engine a live supervisor in this process owns —
|
|
834
|
+
// it may be mid-turn for another session. Only owner-less orphans
|
|
835
|
+
// (force-quit / OS-reaped prior launches) are ours to clean up.
|
|
836
|
+
if (liveEnginePids.has(pid))
|
|
837
|
+
continue;
|
|
838
|
+
// Cross-daemon ownership: command-path matching alone cannot
|
|
839
|
+
// distinguish isolated homes that share a native binary. Unix reparents
|
|
840
|
+
// a true orphan to pid 1. Windows leaves the creator pid on the child,
|
|
841
|
+
// so it is an orphan only when that pid is absent from this same atomic-
|
|
842
|
+
// enough snapshot. PID reuse can cause a conservative false negative,
|
|
843
|
+
// never a live-process kill. Missing PPID preserves legacy test seams.
|
|
844
|
+
if (ppid !== undefined && !isOwnerlessProcess(ppid, livePids, this.platform))
|
|
845
|
+
continue;
|
|
846
|
+
const normalizedCommand = normalizeProcessMatch(command, this.platform);
|
|
847
|
+
const matched = normalizedCommandAnchors.some((anchor) => normalizedCommand === anchor ||
|
|
848
|
+
normalizedCommand.startsWith(`${anchor} `) ||
|
|
849
|
+
normalizedCommand.startsWith(`"${anchor}"`)) || normalizedArgAnchors.some((anchor) => normalizedCommand.includes(anchor));
|
|
850
|
+
if (!matched)
|
|
851
|
+
continue;
|
|
852
|
+
targets.push(pid);
|
|
853
|
+
}
|
|
854
|
+
if (targets.length === 0)
|
|
855
|
+
return 0;
|
|
856
|
+
this.onLog(`${this.logPrefix} reaping ${targets.length} orphan(s) from prior app launches: ${targets.join(', ')}`);
|
|
857
|
+
for (const pid of targets) {
|
|
858
|
+
try {
|
|
859
|
+
this.killProcess(pid, 'SIGKILL');
|
|
860
|
+
}
|
|
861
|
+
catch (err) {
|
|
862
|
+
// Already dead, or not ours to kill — either way, ignore.
|
|
863
|
+
this.onLog(`${this.logPrefix} reap pid=${pid} failed (ignored): ${err instanceof Error ? err.message : err}`);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
// SIGKILL only *requests* termination — block until the reaped pids are
|
|
867
|
+
// actually gone, so a hard-singleton engine isn't spawned while a just-
|
|
868
|
+
// killed orphan still holds the lock. Without this wait the new ds4-server
|
|
869
|
+
// saw the dying orphan and refused to start (the trial then stalled).
|
|
870
|
+
await this.waitForPidsGone(targets, ORPHAN_REAP_WAIT_MS);
|
|
871
|
+
return targets.length;
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Poll the process table until none of `pids` remain, or `timeoutMs`
|
|
875
|
+
* elapses. Best-effort: a still-present pid after the timeout is logged (the
|
|
876
|
+
* subsequent spawn may then fail a singleton check, surfaced normally) rather
|
|
877
|
+
* than thrown, so cleanup never wedges startup.
|
|
878
|
+
*/
|
|
879
|
+
async waitForPidsGone(pids, timeoutMs) {
|
|
880
|
+
if (pids.length === 0)
|
|
881
|
+
return;
|
|
882
|
+
const deadline = Date.now() + timeoutMs;
|
|
883
|
+
let remaining = pids;
|
|
884
|
+
while (remaining.length > 0 && Date.now() < deadline) {
|
|
885
|
+
await sleep(150);
|
|
886
|
+
const alive = new Set((await this.psRunner()).map((p) => p.pid));
|
|
887
|
+
remaining = remaining.filter((pid) => alive.has(pid));
|
|
888
|
+
}
|
|
889
|
+
if (remaining.length > 0) {
|
|
890
|
+
this.onLog(`${this.logPrefix} reaped orphan(s) still present after ${timeoutMs}ms: ${remaining.join(', ')} — spawn may hit the engine's singleton guard`);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Pull every install-specific absolute path out of the launch spec.
|
|
895
|
+
* Used by the orphan reaper to match against `ps` output. The
|
|
896
|
+
* filtering is deliberately conservative: only paths that look like a
|
|
897
|
+
* concrete file (have an extension) qualify, so flag values like
|
|
898
|
+
* `--port 12345` or directory paths passed via `--model` don't get
|
|
899
|
+
* promoted to anchors. Absolute model dirs would still be
|
|
900
|
+
* install-specific in practice, but matching on a directory path
|
|
901
|
+
* could collide with any tool that happens to have it on its command
|
|
902
|
+
* line (e.g. a `du -sh` invocation), so we leave them out.
|
|
903
|
+
*/
|
|
904
|
+
buildOrphanAnchors(launch) {
|
|
905
|
+
const command = [];
|
|
906
|
+
const args = [];
|
|
907
|
+
const seen = new Set();
|
|
908
|
+
const add = (bucket, p) => {
|
|
909
|
+
if (!p || seen.has(p))
|
|
910
|
+
return;
|
|
911
|
+
seen.add(p);
|
|
912
|
+
bucket.push(p);
|
|
913
|
+
};
|
|
914
|
+
if (isAbsoluteProcessPath(launch.command))
|
|
915
|
+
add(command, launch.command);
|
|
916
|
+
for (const arg of launch.args) {
|
|
917
|
+
if (!isAbsoluteProcessPath(arg))
|
|
918
|
+
continue;
|
|
919
|
+
// File-shaped: ends with a dot-extension whose tail contains at
|
|
920
|
+
// least one letter (`gezel_mlx_server.py`, `model.bin`). Skips
|
|
921
|
+
// bare directory paths (`/Users/me/.gezel/.../models/qwen3.6`),
|
|
922
|
+
// including ones whose final segment looks like a version
|
|
923
|
+
// number — `.6`, `1.0.0`, `3.14` would all sneak through a
|
|
924
|
+
// letter-or-digit regex but none describe a real file extension.
|
|
925
|
+
if (!/\.[A-Za-z0-9]*[A-Za-z][A-Za-z0-9]{0,7}$/.test(arg))
|
|
926
|
+
continue;
|
|
927
|
+
add(args, arg);
|
|
928
|
+
}
|
|
929
|
+
return { command, args };
|
|
930
|
+
}
|
|
931
|
+
async waitForReady(launch, signal) {
|
|
932
|
+
const deadline = Date.now() + this.startupTimeoutMs;
|
|
933
|
+
let lastError;
|
|
934
|
+
while (Date.now() < deadline) {
|
|
935
|
+
if (signal.aborted) {
|
|
936
|
+
throw signal.reason instanceof Error
|
|
937
|
+
? signal.reason
|
|
938
|
+
: new Error(`${this.logPrefix} startup aborted`);
|
|
939
|
+
}
|
|
940
|
+
try {
|
|
941
|
+
const res = await this.fetchImpl(`${launch.baseUrl}${this.readinessPath}`, { signal });
|
|
942
|
+
if (res.ok || this.readyOnAnyResponse) {
|
|
943
|
+
// For engines without a dedicated health endpoint, returning
|
|
944
|
+
// any HTTP status proves the port is open and accepting work
|
|
945
|
+
// — that's the readiness signal we care about. Drain the body
|
|
946
|
+
// so the connection isn't left half-open.
|
|
947
|
+
void res.body?.cancel?.().catch(() => { });
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
lastError = new Error(`${this.readinessPath} returned ${res.status}`);
|
|
951
|
+
}
|
|
952
|
+
catch (err) {
|
|
953
|
+
if (signal.aborted) {
|
|
954
|
+
throw signal.reason instanceof Error
|
|
955
|
+
? signal.reason
|
|
956
|
+
: new Error(`${this.logPrefix} startup aborted`);
|
|
957
|
+
}
|
|
958
|
+
lastError = err;
|
|
959
|
+
}
|
|
960
|
+
await new Promise((resolve, reject) => {
|
|
961
|
+
const t = setTimeout(resolve, 500);
|
|
962
|
+
const onAbort = () => {
|
|
963
|
+
clearTimeout(t);
|
|
964
|
+
reject(signal.reason instanceof Error ? signal.reason : new Error('startup aborted'));
|
|
965
|
+
};
|
|
966
|
+
if (signal.aborted)
|
|
967
|
+
onAbort();
|
|
968
|
+
else
|
|
969
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
throw new Error(`${this.logPrefix} did not become ready within ${this.startupTimeoutMs / 1000}s: ${lastError instanceof Error ? lastError.message : String(lastError)}`);
|
|
973
|
+
}
|
|
974
|
+
appendRecentOutput(line) {
|
|
975
|
+
this.currentOutput += `${line}\n`;
|
|
976
|
+
const bytes = Buffer.byteLength(this.currentOutput, 'utf8');
|
|
977
|
+
if (bytes > RECENT_OUTPUT_MAX_BYTES) {
|
|
978
|
+
const trimmed = Buffer.from(this.currentOutput, 'utf8')
|
|
979
|
+
.subarray(bytes - RECENT_OUTPUT_MAX_BYTES)
|
|
980
|
+
.toString('utf8');
|
|
981
|
+
const firstNewline = trimmed.indexOf('\n');
|
|
982
|
+
this.currentOutput = firstNewline >= 0 ? trimmed.slice(firstNewline + 1) : trimmed;
|
|
983
|
+
}
|
|
984
|
+
const panic = classifyNativeEnginePanic(line);
|
|
985
|
+
if (panic &&
|
|
986
|
+
(!this.currentPanic || panicPriority(panic.kind) >= panicPriority(this.currentPanic.kind))) {
|
|
987
|
+
this.currentPanic = panic;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
handleExit(code, signal) {
|
|
991
|
+
void this.releaseCapacity();
|
|
992
|
+
const priorState = this.state;
|
|
993
|
+
const exitedAt = Date.now();
|
|
994
|
+
const expectedReason = this.expectedExitReason;
|
|
995
|
+
const expected = priorState.kind === 'stopped' || expectedReason !== undefined;
|
|
996
|
+
const pid = this.currentPid;
|
|
997
|
+
// A SIGILL is a classification in its own right. Only claim it when
|
|
998
|
+
// the output didn't already explain the death more precisely: an
|
|
999
|
+
// engine that printed `CUDA error: …` and then took SIGILL on the
|
|
1000
|
+
// way down is a CUDA fault, not an unrunnable build.
|
|
1001
|
+
const panic = this.currentPanic ??
|
|
1002
|
+
(signal === 'SIGILL'
|
|
1003
|
+
? {
|
|
1004
|
+
kind: 'illegal-instruction',
|
|
1005
|
+
line: 'engine terminated by SIGILL (illegal instruction) with no diagnostic output',
|
|
1006
|
+
}
|
|
1007
|
+
: undefined);
|
|
1008
|
+
const snapshot = {
|
|
1009
|
+
incidentId: `native-${pid ?? 'unknown'}-${exitedAt}`,
|
|
1010
|
+
...(pid !== undefined ? { pid } : {}),
|
|
1011
|
+
startedAt: this.currentStartedAt || exitedAt,
|
|
1012
|
+
exitedAt,
|
|
1013
|
+
uptimeMs: Math.max(0, exitedAt - (this.currentStartedAt || exitedAt)),
|
|
1014
|
+
code,
|
|
1015
|
+
signal,
|
|
1016
|
+
expected,
|
|
1017
|
+
reachedReady: this.currentReachedReady,
|
|
1018
|
+
...(expectedReason ? { expectedReason } : {}),
|
|
1019
|
+
...(panic ? { panicKind: panic.kind, panicLine: panic.line } : {}),
|
|
1020
|
+
outputTail: this.currentOutput,
|
|
1021
|
+
...(this.currentDiagnostics ? { diagnostics: { ...this.currentDiagnostics } } : {}),
|
|
1022
|
+
};
|
|
1023
|
+
this.lastExit = snapshot;
|
|
1024
|
+
this.onLog(`${this.logPrefix} exit ${JSON.stringify({
|
|
1025
|
+
incidentId: snapshot.incidentId,
|
|
1026
|
+
pid: snapshot.pid ?? null,
|
|
1027
|
+
code,
|
|
1028
|
+
signal: signal ?? null,
|
|
1029
|
+
expected,
|
|
1030
|
+
...(expectedReason ? { expectedReason } : {}),
|
|
1031
|
+
uptimeMs: snapshot.uptimeMs,
|
|
1032
|
+
reachedReady: snapshot.reachedReady,
|
|
1033
|
+
...(snapshot.panicKind ? { panicKind: snapshot.panicKind } : {}),
|
|
1034
|
+
...(snapshot.panicLine ? { panicLine: snapshot.panicLine } : {}),
|
|
1035
|
+
...(snapshot.diagnostics ?? {}),
|
|
1036
|
+
})}`);
|
|
1037
|
+
try {
|
|
1038
|
+
const callbackResult = this.onExit?.({ ...snapshot });
|
|
1039
|
+
if (callbackResult) {
|
|
1040
|
+
void Promise.resolve(callbackResult).catch((err) => {
|
|
1041
|
+
log.warn(`${this.logPrefix} onExit rejected: ${err instanceof Error ? err.message : String(err)}`);
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
catch (err) {
|
|
1046
|
+
log.warn(`${this.logPrefix} onExit threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
1047
|
+
}
|
|
1048
|
+
for (const listener of this.exitListeners)
|
|
1049
|
+
listener(snapshot);
|
|
1050
|
+
// Deregister before any early-return: this owned engine is gone, so
|
|
1051
|
+
// it's free for a future reaper to clean up if it somehow outlives us.
|
|
1052
|
+
if (this.ownedPid !== undefined) {
|
|
1053
|
+
liveEnginePids.delete(this.ownedPid);
|
|
1054
|
+
this.ownedPid = undefined;
|
|
1055
|
+
}
|
|
1056
|
+
this.clearIdleTimer();
|
|
1057
|
+
this.clearFreezeTimer();
|
|
1058
|
+
this.clearHealthTimer();
|
|
1059
|
+
this.currentPid = undefined;
|
|
1060
|
+
this.currentStartedAt = 0;
|
|
1061
|
+
this.currentReachedReady = false;
|
|
1062
|
+
this.currentDiagnostics = undefined;
|
|
1063
|
+
this.currentOutput = '';
|
|
1064
|
+
this.currentPanic = undefined;
|
|
1065
|
+
this.expectedExitReason = undefined;
|
|
1066
|
+
if (priorState.kind === 'stopped')
|
|
1067
|
+
return;
|
|
1068
|
+
this.onLog(`${this.logPrefix} exited (code=${code} signal=${signal ?? 'none'})`);
|
|
1069
|
+
// If the child died while we were still waiting for /health to come
|
|
1070
|
+
// up, abort the startup wait immediately. Otherwise `waitForReady`
|
|
1071
|
+
// would keep polling a dead port for the full startup-timeout
|
|
1072
|
+
// budget (default 180s) — observed as a 3-minute wedge in chat
|
|
1073
|
+
// when an unclassified Python exception kills the engine before
|
|
1074
|
+
// ready. The provider's stdout classifier may still produce a
|
|
1075
|
+
// better-typed error a moment later via `abortStartup(err)`; that
|
|
1076
|
+
// call wins because it lands first (the classifier sees the leaf
|
|
1077
|
+
// exception line slightly before the kernel reports the exit).
|
|
1078
|
+
if (this.startupAbort && priorState.kind === 'starting') {
|
|
1079
|
+
this.startupAbort.abort(new Error(`${this.logPrefix} child exited (code=${code} signal=${signal ?? 'none'}) before becoming ready`));
|
|
1080
|
+
}
|
|
1081
|
+
this.state = { kind: 'stopped' };
|
|
1082
|
+
}
|
|
1083
|
+
startHealthWatch() {
|
|
1084
|
+
this.clearHealthTimer();
|
|
1085
|
+
this.healthTimer = setInterval(() => {
|
|
1086
|
+
void this.healthProbe();
|
|
1087
|
+
void this.pressureProbe();
|
|
1088
|
+
}, this.healthIntervalMs);
|
|
1089
|
+
this.healthTimer.unref?.();
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* Accelerator pressure this engine is currently sitting inside, or null when
|
|
1093
|
+
* there is none. Read by callers that want to explain a symptom the engine
|
|
1094
|
+
* itself cannot see — a turn whose throughput collapsed has no way to know
|
|
1095
|
+
* the card is oversubscribed, and "the engine is slow" is the wrong story.
|
|
1096
|
+
*/
|
|
1097
|
+
memoryPressureHeld() {
|
|
1098
|
+
if (this.pressureHeldSince === undefined)
|
|
1099
|
+
return null;
|
|
1100
|
+
return {
|
|
1101
|
+
sinceAwakeMs: this.pressureHeldSince,
|
|
1102
|
+
heldMs: Math.max(0, awakeNow() - this.pressureHeldSince),
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
engineBusy() {
|
|
1106
|
+
try {
|
|
1107
|
+
return this.activeRequests > 0 || this.isBusy?.() === true;
|
|
1108
|
+
}
|
|
1109
|
+
catch (error) {
|
|
1110
|
+
this.onLog(`${this.logPrefix} busy predicate failed: ${error instanceof Error ? error.message : error}`);
|
|
1111
|
+
return true;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
async pressureProbe() {
|
|
1115
|
+
if ((!this.memoryPressure && !this.capacityLease) ||
|
|
1116
|
+
this.pressureCheckInFlight ||
|
|
1117
|
+
this.state.kind !== 'running')
|
|
1118
|
+
return;
|
|
1119
|
+
this.pressureCheckInFlight = true;
|
|
1120
|
+
try {
|
|
1121
|
+
const requested = (await this.capacityLease?.shouldYield()) ?? false;
|
|
1122
|
+
if (requested && this.yieldFlight)
|
|
1123
|
+
return;
|
|
1124
|
+
const pressure = requested
|
|
1125
|
+
? { pressured: true, detail: 'another engine is waiting for memory' }
|
|
1126
|
+
: ((await this.memoryPressure?.()) ?? { pressured: false });
|
|
1127
|
+
if (!pressure.pressured || this.state.kind !== 'running') {
|
|
1128
|
+
this.pressureDeadlineAt = undefined;
|
|
1129
|
+
this.pressureHeldSince = undefined;
|
|
1130
|
+
this.pressureHeldLoggedAt = undefined;
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
const now = awakeNow();
|
|
1134
|
+
if (this.pressureHeldSince === undefined)
|
|
1135
|
+
this.pressureHeldSince = now;
|
|
1136
|
+
this.pressureDeadlineAt = this.lastUsedAwakeAt + this.pressureIdleTimeoutMs;
|
|
1137
|
+
const busy = this.engineBusy();
|
|
1138
|
+
if ((!requested && now < this.pressureDeadlineAt) || busy) {
|
|
1139
|
+
// The only lever here is "stop MY engine once MY engine is idle", and
|
|
1140
|
+
// a busy one is out of reach of it. Saying so beats the silence that
|
|
1141
|
+
// preceded it: a four-hour turn on an oversubscribed card ran this
|
|
1142
|
+
// probe about 960 times and emitted nothing at all, so the machine
|
|
1143
|
+
// state that made every other tenant unusable left no trace in the
|
|
1144
|
+
// log. Rate-limited because pressure is normal and steady on a card
|
|
1145
|
+
// sized to one model.
|
|
1146
|
+
if (this.pressureHeldLoggedAt === undefined ||
|
|
1147
|
+
now - this.pressureHeldLoggedAt >= PRESSURE_HELD_LOG_INTERVAL_MS) {
|
|
1148
|
+
this.pressureHeldLoggedAt = now;
|
|
1149
|
+
const held = Math.round((now - this.pressureHeldSince) / 1000);
|
|
1150
|
+
const why = busy ? 'engine is serving a turn' : 'still inside the warm-retention grace';
|
|
1151
|
+
const detail = pressure.detail ? ` (${pressure.detail})` : '';
|
|
1152
|
+
this.onLog(`${this.logPrefix} memory pressure${detail} held ${held}s — ${why}, not releasing`);
|
|
1153
|
+
}
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
if (requested) {
|
|
1157
|
+
// Use the same gate as incoming requests, including while flushing.
|
|
1158
|
+
// A new request must not enter the engine between idle detection and
|
|
1159
|
+
// the awaited cache flush that precedes releasing its memory.
|
|
1160
|
+
await this.yieldForWaitingCapacity();
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
this.onLog(`${this.logPrefix} memory pressure${pressure.detail ? ` (${pressure.detail})` : ''} — flushing and stopping idle engine`);
|
|
1164
|
+
await this.runFreeze();
|
|
1165
|
+
if (this.engineBusy())
|
|
1166
|
+
return;
|
|
1167
|
+
await this.stop('memory-pressure');
|
|
1168
|
+
}
|
|
1169
|
+
catch (error) {
|
|
1170
|
+
this.onLog(`${this.logPrefix} memory-pressure check failed: ${error instanceof Error ? error.message : error}`);
|
|
1171
|
+
}
|
|
1172
|
+
finally {
|
|
1173
|
+
this.pressureCheckInFlight = false;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
async releaseCapacity() {
|
|
1177
|
+
const lease = this.capacityLease;
|
|
1178
|
+
this.capacityLease = undefined;
|
|
1179
|
+
await lease
|
|
1180
|
+
?.release()
|
|
1181
|
+
.catch((error) => this.onLog(`${this.logPrefix} memory lease release failed: ${error}`));
|
|
1182
|
+
}
|
|
1183
|
+
async healthProbe() {
|
|
1184
|
+
if (this.state.kind !== 'running')
|
|
1185
|
+
return;
|
|
1186
|
+
const launch = this.state.launch;
|
|
1187
|
+
try {
|
|
1188
|
+
const res = await this.fetchImpl(`${launch.baseUrl}${this.readinessPath}`);
|
|
1189
|
+
if (res.ok || this.readyOnAnyResponse) {
|
|
1190
|
+
void res.body?.cancel?.().catch(() => { });
|
|
1191
|
+
if (this.state.kind === 'running')
|
|
1192
|
+
this.state.healthFails = 0;
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
catch {
|
|
1197
|
+
/* treat as failure below */
|
|
1198
|
+
}
|
|
1199
|
+
if (this.state.kind !== 'running')
|
|
1200
|
+
return;
|
|
1201
|
+
// A busy engine can be too busy to answer: a 12-image vision prefill held
|
|
1202
|
+
// the MLX server's event loop, three probes went unanswered, and the
|
|
1203
|
+
// restart threw away a 20-minute powerpoint-deck evaluate turn
|
|
1204
|
+
// (qwen3.8-27b, 2026-09-23). A turn in flight has its own awake-time
|
|
1205
|
+
// deadlines that end a genuinely wedged engine, so only an idle engine's
|
|
1206
|
+
// silence counts here.
|
|
1207
|
+
if (this.engineBusy())
|
|
1208
|
+
return;
|
|
1209
|
+
this.state.healthFails++;
|
|
1210
|
+
if (this.state.healthFails >= HEALTH_FAIL_THRESHOLD) {
|
|
1211
|
+
this.onLog(`${this.logPrefix} ${HEALTH_FAIL_THRESHOLD} health failures — restarting`);
|
|
1212
|
+
await this.stop('health-restart');
|
|
1213
|
+
try {
|
|
1214
|
+
await this.ensureRunning();
|
|
1215
|
+
}
|
|
1216
|
+
catch (err) {
|
|
1217
|
+
this.onLog(`${this.logPrefix} restart failed: ${err instanceof Error ? err.message : err}`);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
resetIdleTimer() {
|
|
1222
|
+
this.clearIdleTimer();
|
|
1223
|
+
this.clearFreezeTimer();
|
|
1224
|
+
this.freezeFired = false;
|
|
1225
|
+
// Stage 1 (freeze): persist transient state to disk while the
|
|
1226
|
+
// child is still healthy. Doesn't stop the engine. Skipped when
|
|
1227
|
+
// freezeTimeoutMs is unset/0 (legacy single-stage behavior) or
|
|
1228
|
+
// when no onFreeze callback was supplied.
|
|
1229
|
+
if (this.freezeTimeoutMs > 0 && this.onFreeze && this.freezeTimeoutMs < this.idleTimeoutMs) {
|
|
1230
|
+
this.freezeTimer = setTimeout(() => {
|
|
1231
|
+
// Fire-and-forget; supervisor logs + swallows any error so a
|
|
1232
|
+
// bad callback can't crash the host.
|
|
1233
|
+
void this.runFreeze();
|
|
1234
|
+
}, this.freezeTimeoutMs + 50);
|
|
1235
|
+
this.freezeTimer.unref?.();
|
|
1236
|
+
}
|
|
1237
|
+
// Stage 2 (idle): full SIGTERM. Existing behavior — runs after
|
|
1238
|
+
// idleTimeoutMs regardless of whether Stage 1 fired.
|
|
1239
|
+
this.armIdleTimer(this.idleTimeoutMs);
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Arm the VRAM-release timer to fire `delayMs` from now, then re-check
|
|
1243
|
+
* against the AWAKE clock when it does.
|
|
1244
|
+
*
|
|
1245
|
+
* The re-check is the load-bearing part. A host suspension fires every
|
|
1246
|
+
* overdue timer the instant the machine resumes, so a nap longer than the
|
|
1247
|
+
* idle timeout used to read as "this engine has been idle that long" and
|
|
1248
|
+
* unload it — observed unloading a resident 27B model on each ~16 min
|
|
1249
|
+
* dark-wake cycle, so the next turn paid a full cold load for idleness
|
|
1250
|
+
* nobody could have avoided. Re-arming for the shortfall matters just as
|
|
1251
|
+
* much: an early return with no re-arm would leave the engine resident for
|
|
1252
|
+
* good, trading a spurious unload for a permanent leak.
|
|
1253
|
+
*/
|
|
1254
|
+
armIdleTimer(delayMs) {
|
|
1255
|
+
this.clearIdleTimer();
|
|
1256
|
+
if (this.idleTimeoutMs <= 0)
|
|
1257
|
+
return;
|
|
1258
|
+
this.idleTimer = setTimeout(() => {
|
|
1259
|
+
const since = awakeNow() - this.lastUsedAwakeAt;
|
|
1260
|
+
if (since < this.idleTimeoutMs) {
|
|
1261
|
+
this.armIdleTimer(this.idleTimeoutMs - since);
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
// Don't interrupt an actual native request. Logical turns parked on a
|
|
1265
|
+
// tool or a person are intentionally not busy here; a later iteration
|
|
1266
|
+
// can lazy-start the engine again from its flushed disk cache.
|
|
1267
|
+
if (this.engineBusy()) {
|
|
1268
|
+
this.onLog(`${this.logPrefix} idle deadline reached but an engine request is active — deferring VRAM stop`);
|
|
1269
|
+
this.resetIdleTimer();
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
this.onLog(`${this.logPrefix} idle timeout — stopping to free VRAM`);
|
|
1273
|
+
void this.stop('idle');
|
|
1274
|
+
}, delayMs + 50);
|
|
1275
|
+
this.idleTimer.unref?.();
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Project an awake-clock instant onto the wall clock for display. The UI
|
|
1279
|
+
* renders a countdown, and a deadline held in awake time would otherwise
|
|
1280
|
+
* show as an already-past timestamp after any suspension.
|
|
1281
|
+
*/
|
|
1282
|
+
wallClockFor(awakeInstant) {
|
|
1283
|
+
return Date.now() + (awakeInstant - awakeNow());
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* Run the user-supplied onFreeze hook with proper guarding. Marks
|
|
1287
|
+
* `freezeFired` so subsequent ticks within this idle window don't
|
|
1288
|
+
* re-fire (idempotency for callers, e.g. a re-entrant flush would
|
|
1289
|
+
* double-write the same content).
|
|
1290
|
+
*/
|
|
1291
|
+
async runFreeze() {
|
|
1292
|
+
if (this.freezeFired)
|
|
1293
|
+
return;
|
|
1294
|
+
// Cache endpoints share the native server with inference; do not flush a
|
|
1295
|
+
// slot while that engine is actively reading or writing it. The next idle
|
|
1296
|
+
// deadline (or pressure probe) gets another chance.
|
|
1297
|
+
if (this.engineBusy())
|
|
1298
|
+
return;
|
|
1299
|
+
this.freezeFired = true;
|
|
1300
|
+
if (!this.onFreeze)
|
|
1301
|
+
return;
|
|
1302
|
+
this.onLog(`${this.logPrefix} idle freeze — flushing caches to disk (model stays resident)`);
|
|
1303
|
+
try {
|
|
1304
|
+
await this.onFreeze();
|
|
1305
|
+
}
|
|
1306
|
+
catch (err) {
|
|
1307
|
+
this.onLog(`${this.logPrefix} freeze callback threw: ${err instanceof Error ? err.message : err}`);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
clearIdleTimer() {
|
|
1311
|
+
if (this.idleTimer) {
|
|
1312
|
+
clearTimeout(this.idleTimer);
|
|
1313
|
+
this.idleTimer = undefined;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
clearFreezeTimer() {
|
|
1317
|
+
if (this.freezeTimer) {
|
|
1318
|
+
clearTimeout(this.freezeTimer);
|
|
1319
|
+
this.freezeTimer = undefined;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
clearHealthTimer() {
|
|
1323
|
+
if (this.healthTimer) {
|
|
1324
|
+
clearInterval(this.healthTimer);
|
|
1325
|
+
this.healthTimer = undefined;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
/** High-precision fatal signatures; attribution happens only if the child exits. */
|
|
1330
|
+
export function classifyNativeEnginePanic(line) {
|
|
1331
|
+
const clipped = line.trim().slice(0, 1_000);
|
|
1332
|
+
if (/CUDA error:\s*invalid (?:configuration )?argument/i.test(line)) {
|
|
1333
|
+
return { kind: 'cuda-invalid-argument', line: clipped };
|
|
1334
|
+
}
|
|
1335
|
+
if (/CUDA error:\s*(?:out of memory|memory allocation)/i.test(line)) {
|
|
1336
|
+
return { kind: 'cuda-out-of-memory', line: clipped };
|
|
1337
|
+
}
|
|
1338
|
+
// Vulkan's spellings of the same condition — the backend a non-CUDA
|
|
1339
|
+
// discrete card (or a CUDA card without nvidia-smi) runs on.
|
|
1340
|
+
if (/ErrorOutOfDeviceMemory|OutOfDeviceMemoryError|ggml_vulkan:.*(?:allocation|memory).*failed/i.test(line)) {
|
|
1341
|
+
return { kind: 'vulkan-out-of-memory', line: clipped };
|
|
1342
|
+
}
|
|
1343
|
+
if (/CUDA error:\s*(?:an )?illegal memory access/i.test(line)) {
|
|
1344
|
+
return { kind: 'cuda-illegal-memory-access', line: clipped };
|
|
1345
|
+
}
|
|
1346
|
+
if (/CUDA error:.*device-side assert/i.test(line)) {
|
|
1347
|
+
return { kind: 'cuda-device-assert', line: clipped };
|
|
1348
|
+
}
|
|
1349
|
+
if (/CUDA error:/i.test(line))
|
|
1350
|
+
return { kind: 'cuda-error', line: clipped };
|
|
1351
|
+
if (/assert(?:ion)? failed|GGML_ASSERT/i.test(line)) {
|
|
1352
|
+
return { kind: 'assertion-failed', line: clipped };
|
|
1353
|
+
}
|
|
1354
|
+
return undefined;
|
|
1355
|
+
}
|
|
1356
|
+
function panicPriority(kind) {
|
|
1357
|
+
if (kind === 'cuda-error')
|
|
1358
|
+
return 1;
|
|
1359
|
+
if (kind === 'assertion-failed')
|
|
1360
|
+
return 2;
|
|
1361
|
+
return 3;
|
|
1362
|
+
}
|
|
1363
|
+
/**
|
|
1364
|
+
* Default process snapshot. PPID is load-bearing: command paths identify
|
|
1365
|
+
* which engine a row belongs to, while the parent snapshot distinguishes an
|
|
1366
|
+
* owner-less prior-launch orphan from an engine actively owned by another
|
|
1367
|
+
* gezeld process. Errors remain best-effort so cleanup cannot block startup.
|
|
1368
|
+
*/
|
|
1369
|
+
async function defaultPsRunner(platform) {
|
|
1370
|
+
try {
|
|
1371
|
+
return await listProcessSnapshots({ platform });
|
|
1372
|
+
}
|
|
1373
|
+
catch {
|
|
1374
|
+
return [];
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
function isOwnerlessProcess(ppid, livePids, platform) {
|
|
1378
|
+
return platform === 'win32' ? !livePids.has(ppid) : ppid <= 1;
|
|
1379
|
+
}
|
|
1380
|
+
function normalizeProcessMatch(value, platform) {
|
|
1381
|
+
return platform === 'win32' ? value.replaceAll('/', '\\').toLowerCase() : value;
|
|
1382
|
+
}
|
|
1383
|
+
function isAbsoluteProcessPath(value) {
|
|
1384
|
+
// Accept both syntaxes independent of the host running the test/build. The
|
|
1385
|
+
// launch value itself is authoritative, and cross-platform fixtures exercise
|
|
1386
|
+
// Windows paths on Unix CI and Unix paths on Windows CI.
|
|
1387
|
+
return value.startsWith('/') || /^(?:[A-Za-z]:[\\/]|\\\\)/.test(value);
|
|
1388
|
+
}
|
|
1389
|
+
/** Parse the portable `ps -axo pid=,ppid=,command=` shape. */
|
|
1390
|
+
export function parseNativeProcessSnapshot(stdout) {
|
|
1391
|
+
const out = [];
|
|
1392
|
+
for (const rawLine of stdout.split('\n')) {
|
|
1393
|
+
const line = rawLine.trimStart();
|
|
1394
|
+
if (!line)
|
|
1395
|
+
continue;
|
|
1396
|
+
const m = line.match(/^(\d+)\s+(\d+)\s+(.*)$/);
|
|
1397
|
+
if (!m)
|
|
1398
|
+
continue;
|
|
1399
|
+
const pid = Number.parseInt(m[1], 10);
|
|
1400
|
+
const ppid = Number.parseInt(m[2], 10);
|
|
1401
|
+
if (!Number.isFinite(pid) || !Number.isFinite(ppid))
|
|
1402
|
+
continue;
|
|
1403
|
+
out.push({ pid, ppid, command: m[3] });
|
|
1404
|
+
}
|
|
1405
|
+
return out;
|
|
1406
|
+
}
|
|
1407
|
+
/** `code` stamped on every engine-launch failure. See {@link isEngineLaunchError}. */
|
|
1408
|
+
export const ENGINE_LAUNCH_ERROR_CODE = 'engine-launch';
|
|
1409
|
+
/**
|
|
1410
|
+
* True when the engine binary itself never started, however deep the error
|
|
1411
|
+
* was rewrapped. Matches the code first, then falls back to the stable
|
|
1412
|
+
* message so a rewrapped error still classifies right (same belt-and-braces
|
|
1413
|
+
* shape as the pool's `engine-busy` matcher).
|
|
1414
|
+
*/
|
|
1415
|
+
export function isEngineLaunchError(err) {
|
|
1416
|
+
if (err &&
|
|
1417
|
+
typeof err === 'object' &&
|
|
1418
|
+
err.code === ENGINE_LAUNCH_ERROR_CODE) {
|
|
1419
|
+
return true;
|
|
1420
|
+
}
|
|
1421
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1422
|
+
return /could not launch /.test(message);
|
|
1423
|
+
}
|
|
1424
|
+
function nativeSpawnError(logPrefix, launch, cause) {
|
|
1425
|
+
const code = cause && typeof cause === 'object' && 'code' in cause && typeof cause.code === 'string'
|
|
1426
|
+
? cause.code
|
|
1427
|
+
: undefined;
|
|
1428
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
1429
|
+
const err = new Error(`${logPrefix} could not launch ${basename(launch.command)}${code ? ` (${code})` : ''}. ` +
|
|
1430
|
+
`Executable: ${launch.command}${launch.cwd ? `; cwd: ${launch.cwd}` : ''}. ${detail}`, { cause });
|
|
1431
|
+
// Consumers that report *why* a model failed need to tell "the engine
|
|
1432
|
+
// never came up" apart from "the model answered badly". The engine starts
|
|
1433
|
+
// lazily on the first turn, so without this marker a launch failure looks
|
|
1434
|
+
// like a generation failure — which is how the fitness proeve came to
|
|
1435
|
+
// report "engine spawned and served the probe session" for a model whose
|
|
1436
|
+
// engine had never started.
|
|
1437
|
+
return Object.assign(err, { code: ENGINE_LAUNCH_ERROR_CODE });
|
|
1438
|
+
}
|
|
1439
|
+
async function killGracefully(child) {
|
|
1440
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
1441
|
+
return;
|
|
1442
|
+
const exited = new Promise((resolve) => child.once('exit', () => resolve()));
|
|
1443
|
+
try {
|
|
1444
|
+
child.kill('SIGTERM');
|
|
1445
|
+
}
|
|
1446
|
+
catch {
|
|
1447
|
+
/* already dead */
|
|
1448
|
+
}
|
|
1449
|
+
const timeout = setTimeout(() => {
|
|
1450
|
+
try {
|
|
1451
|
+
child.kill('SIGKILL');
|
|
1452
|
+
}
|
|
1453
|
+
catch {
|
|
1454
|
+
/* */
|
|
1455
|
+
}
|
|
1456
|
+
}, 3_000);
|
|
1457
|
+
timeout.unref?.();
|
|
1458
|
+
await exited;
|
|
1459
|
+
clearTimeout(timeout);
|
|
1460
|
+
}
|
|
1461
|
+
function sleep(ms) {
|
|
1462
|
+
return new Promise((resolve) => {
|
|
1463
|
+
const t = setTimeout(resolve, ms);
|
|
1464
|
+
t.unref?.();
|
|
1465
|
+
});
|
|
1466
|
+
}
|