xinfer-ai 0.12.1

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/docs/xbot.md ADDED
@@ -0,0 +1,110 @@
1
+ # xbot + xInfer
2
+
3
+ This guide connects `xbot` directly to `xInfer` through the built-in OpenAI-compatible `/v1/chat/completions` endpoint.
4
+
5
+ ```
6
+ xbot -> xInfer (OpenAI-compatible)
7
+ ```
8
+
9
+ ## 1) Start xInfer (at port 8000)
10
+
11
+ ```bash
12
+ ./run --release --features cuda,nccl,flashinfer,cutlass \
13
+ --m Qwen/Qwen3.6-27B-FP8
14
+ ```
15
+
16
+ or
17
+
18
+ ```shell
19
+ # Python
20
+ python3 -m xinfer.server --m Qwen/Qwen3.6-27B-FP8
21
+ ```
22
+
23
+ ## 2) Discover the served model name
24
+
25
+ ```bash
26
+ curl http://localhost:8000/v1/models
27
+ ```
28
+
29
+ Use the returned `id` in the xbot config.
30
+
31
+ ## 3) Configure xbot
32
+
33
+ Install xbot:
34
+
35
+ ```bash
36
+ npm install -g @trusted-ai/xbot
37
+ ```
38
+ Or:
39
+
40
+ ```bash
41
+ cargo install xbot
42
+ ```
43
+
44
+ Run xbot provider config:
45
+
46
+ ```bash
47
+ xbot onboard
48
+ xbot config --provider
49
+ ```
50
+
51
+ Refer to the following selections:
52
+
53
+ ```shell
54
+ > Select provider to configure: custom
55
+ > Enter a unique name for this custom provider: xinfer
56
+ > Enter API Base URL (e.g. https://api.yourprovider.com/v1): http://localhost:8000/v1/
57
+ > Enter API Key? (Optional for local/custom) No
58
+
59
+ Fetching available models...
60
+ > Select default model: Qwen3.6-27B-FP8
61
+ Using contextWindowTokens from model metadata: 262144
62
+
63
+ > Configure subagent provider/model now? No
64
+
65
+ Configuration saved successfully!
66
+ Config file: /root/.xbot/config.json
67
+
68
+ Final Provider Config:
69
+ {
70
+ "api_key": "",
71
+ "api_base": "http://localhost:8000/v1/",
72
+ "extra_headers": {},
73
+ "reasoning_effort": null
74
+ }
75
+ ```
76
+
77
+ ## 4) Run xbot
78
+
79
+ One-shot task
80
+
81
+ ```bash
82
+ cd YOUR PROJECT
83
+ # scan project and init agent file
84
+ xbot chat /init
85
+ # real task
86
+ xbot chat "find bugs in this project."
87
+ ```
88
+ **Interactive terminal** (rich TUI)
89
+
90
+ ```bash
91
+ cd YOUR PROJECT
92
+ # you may add .xbot workspace folder to .gitignore
93
+ xbot repl
94
+ ```
95
+
96
+ ## Notes
97
+
98
+ - Tool calls follow the normal OpenAI request/response loop.
99
+ - For Qwen coder models, `--enforce-parser qwen_coder` is usually the most reliable parser setting.
100
+ - If xbot reports a model mismatch, compare your configured model against `GET /v1/models`.
101
+
102
+ ## Troubleshooting
103
+
104
+ Chat logger:
105
+
106
+ ```bash
107
+ # Log into files (in folder ./log)
108
+ export XINFER_CHAT_LOGGER=1
109
+ ```
110
+
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "xinfer-ai",
3
+ "version": "0.12.1",
4
+ "description": "xInfer — a high-performance LLM inference engine in Rust with CUDA/Metal acceleration",
5
+ "license": "MIT",
6
+ "homepage": "https://github.com/guoqingbao/xinfer#readme",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/guoqingbao/xinfer.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/guoqingbao/xinfer/issues"
13
+ },
14
+ "bin": {
15
+ "xinfer": "bin/xinfer.js"
16
+ },
17
+ "scripts": {
18
+ "prepack": "node scripts/prepare-package.js",
19
+ "postpack": "node scripts/clean-package.js",
20
+ "postinstall": "node scripts/install.js"
21
+ },
22
+ "files": [
23
+ "bin/xinfer.js",
24
+ "scripts/install.js",
25
+ "docs",
26
+ "README.md",
27
+ "LICENSE.txt"
28
+ ],
29
+ "keywords": [
30
+ "llm",
31
+ "inference",
32
+ "cuda",
33
+ "metal",
34
+ "gpu",
35
+ "xinfer",
36
+ "rust"
37
+ ],
38
+ "os": [
39
+ "darwin",
40
+ "linux"
41
+ ],
42
+ "cpu": [
43
+ "arm64",
44
+ "x64"
45
+ ],
46
+ "engines": {
47
+ "node": ">=18"
48
+ }
49
+ }
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const fs = require("fs");
5
+ const https = require("https");
6
+ const os = require("os");
7
+ const path = require("path");
8
+ const { createHash } = require("crypto");
9
+ const { spawnSync } = require("child_process");
10
+
11
+ const pkg = require("../package.json");
12
+
13
+ function targetTriple() {
14
+ const platform = process.platform;
15
+ const arch = process.arch;
16
+ if (
17
+ (platform !== "linux" && platform !== "darwin") ||
18
+ (arch !== "x64" && arch !== "arm64")
19
+ ) {
20
+ throw new Error(
21
+ `Unsupported platform: ${platform}-${arch}. Supported: linux/darwin x64/arm64.`
22
+ );
23
+ }
24
+ return `${platform}-${arch}`;
25
+ }
26
+
27
+ /**
28
+ * Detect CUDA compute capability via nvidia-smi.
29
+ * Returns the highest SM version across all GPUs (e.g. "90", "89", "80").
30
+ * Returns null if no NVIDIA GPU detected (e.g. macOS Metal).
31
+ */
32
+ function detectCudaComputeCap() {
33
+ // Allow explicit override
34
+ if (process.env.XINFER_CUDA_COMPUTE_CAP) {
35
+ return process.env.XINFER_CUDA_COMPUTE_CAP;
36
+ }
37
+
38
+ try {
39
+ const result = spawnSync(
40
+ "nvidia-smi",
41
+ [
42
+ "--query-gpu=compute_cap",
43
+ "--format=csv,noheader,nounits",
44
+ ],
45
+ { encoding: "utf8", timeout: 10000 }
46
+ );
47
+ if (result.status !== 0 || !result.stdout) return null;
48
+
49
+ let maxCap = 0;
50
+ for (const line of result.stdout.trim().split("\n")) {
51
+ const cap = line.trim();
52
+ if (!cap) continue;
53
+ // Parse "8.9" -> 89, "9.0" -> 90, "10.0" -> 100
54
+ const parts = cap.split(".");
55
+ const major = parseInt(parts[0], 10);
56
+ const minor = parseInt(parts[1] || "0", 10);
57
+ const sm = major * 10 + minor;
58
+ if (sm > maxCap) maxCap = sm;
59
+ }
60
+ return maxCap > 0 ? String(maxCap) : null;
61
+ } catch {
62
+ return null;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Map detected SM version to the build variant name.
68
+ * Build variants match CI matrix: sm70, sm75, sm80, sm89, sm90, sm120
69
+ */
70
+ function smToVariant(sm) {
71
+ const n = parseInt(sm, 10);
72
+ if (n >= 120) return "sm120";
73
+ if (n >= 90) return "sm90";
74
+ if (n >= 89) return "sm89";
75
+ if (n >= 80) return "sm80";
76
+ if (n >= 75) return "sm75";
77
+ if (n >= 70) return "sm70";
78
+ return "sm80";
79
+ }
80
+
81
+ function download(url, destination, redirects = 0) {
82
+ if (redirects > 5) {
83
+ return Promise.reject(
84
+ new Error(`Too many redirects while downloading ${url}`)
85
+ );
86
+ }
87
+ return new Promise((resolve, reject) => {
88
+ const proto = url.startsWith("https") ? https : require("http");
89
+ const request = proto.get(url, (response) => {
90
+ if (
91
+ response.statusCode >= 300 &&
92
+ response.statusCode < 400 &&
93
+ response.headers.location
94
+ ) {
95
+ response.resume();
96
+ const next = new URL(response.headers.location, url).toString();
97
+ download(next, destination, redirects + 1).then(resolve, reject);
98
+ return;
99
+ }
100
+ if (response.statusCode !== 200) {
101
+ response.resume();
102
+ reject(
103
+ new Error(`Download failed (${response.statusCode}) for ${url}`)
104
+ );
105
+ return;
106
+ }
107
+ const file = fs.createWriteStream(destination);
108
+ response.pipe(file);
109
+ file.on("finish", () => file.close(resolve));
110
+ file.on("error", reject);
111
+ });
112
+ request.on("error", reject);
113
+ });
114
+ }
115
+
116
+ function sha256(file) {
117
+ const hash = createHash("sha256");
118
+ hash.update(fs.readFileSync(file));
119
+ return hash.digest("hex");
120
+ }
121
+
122
+ function expectedSha(manifest, artifactName) {
123
+ for (const line of manifest.split(/\r?\n/)) {
124
+ const trimmed = line.trim();
125
+ if (!trimmed) continue;
126
+ const parts = trimmed.split(/\s+/);
127
+ const checksum = parts[0];
128
+ const name = parts[parts.length - 1].replace(/^\*/, "");
129
+ if (name === artifactName) return checksum;
130
+ }
131
+ return null;
132
+ }
133
+
134
+ async function main() {
135
+ const target = targetTriple();
136
+ const version = process.env.XINFER_INSTALL_VERSION || pkg.version;
137
+ const tag = process.env.XINFER_INSTALL_TAG || `v${version}`;
138
+ const base =
139
+ process.env.XINFER_INSTALL_BASE_URL ||
140
+ `https://github.com/guoqingbao/xinfer/releases/download/${tag}`;
141
+
142
+ let variant = "";
143
+ if (process.platform === "linux") {
144
+ const sm = detectCudaComputeCap();
145
+ if (sm) {
146
+ variant = `-${smToVariant(sm)}`;
147
+ console.log(
148
+ `Detected CUDA compute capability: sm_${sm} -> variant${variant}`
149
+ );
150
+ } else {
151
+ console.log(
152
+ "No NVIDIA GPU detected; downloading default (sm80) build."
153
+ );
154
+ variant = "-sm80";
155
+ }
156
+ }
157
+ // macOS uses metal variant
158
+ if (process.platform === "darwin") {
159
+ variant = "-metal";
160
+ }
161
+
162
+ const artifactName = `xinfer-${version}-${target}${variant}.tar.gz`;
163
+ const artifactUrl = `${base}/${artifactName}`;
164
+ const sumsUrl = `${base}/SHA256SUMS`;
165
+ const root = path.resolve(__dirname, "..");
166
+ const installDir = path.join(root, "vendor", `${target}${variant}`);
167
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "xinfer-install-"));
168
+ const archivePath = path.join(tmpDir, artifactName);
169
+ const sumsPath = path.join(tmpDir, "SHA256SUMS");
170
+
171
+ try {
172
+ console.log(`Downloading ${artifactUrl}...`);
173
+ await download(sumsUrl, sumsPath);
174
+ await download(artifactUrl, archivePath);
175
+ const expected = expectedSha(
176
+ fs.readFileSync(sumsPath, "utf8"),
177
+ artifactName
178
+ );
179
+ if (!expected) {
180
+ throw new Error(`No checksum entry found for ${artifactName}`);
181
+ }
182
+ const actual = sha256(archivePath);
183
+ if (actual !== expected) {
184
+ throw new Error(
185
+ `Checksum mismatch for ${artifactName}: expected ${expected}, got ${actual}`
186
+ );
187
+ }
188
+
189
+ fs.rmSync(installDir, { recursive: true, force: true });
190
+ fs.mkdirSync(installDir, { recursive: true });
191
+ const tar = spawnSync(
192
+ "tar",
193
+ ["-xzf", archivePath, "-C", installDir],
194
+ { stdio: "inherit" }
195
+ );
196
+ if (tar.error) throw tar.error;
197
+ if (tar.status !== 0) {
198
+ throw new Error(`tar exited with status ${tar.status}`);
199
+ }
200
+ const binary = path.join(installDir, "xinfer");
201
+ fs.chmodSync(binary, 0o755);
202
+ console.log(`xinfer installed to ${installDir}`);
203
+ } finally {
204
+ fs.rmSync(tmpDir, { recursive: true, force: true });
205
+ }
206
+ }
207
+
208
+ main().catch((err) => {
209
+ console.error(`xinfer install failed: ${err.message}`);
210
+ process.exit(1);
211
+ });