@hypabolic/crossbar 0.1.0 → 0.1.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.
@@ -7,7 +7,7 @@ adapter registers under (`oai` = `openai-completions`, `ant` = `anthropic-messag
7
7
  | Backend | port | pi api | listModels | introspectLoaded | switchModel | loadUnload | auth | health | perModelCaps | streaming | discovery fingerprint |
8
8
  |---|---|---|---|---|---|---|---|---|---|---|---|
9
9
  | **Ollama** | 11434 | oai | ✅ `/api/tags`,`/v1/models` | ✅ `/api/ps` | ✅ implicit (request id) | ✅ `keep_alive:0` | ◐ none local | ✅ `GET /` text | ✅ `/api/show` caps + ctx | ✅ | `GET /` → `Ollama is running` |
10
- | **LM Studio** | 1234 | oai | ✅ `/api/v0/models`,`/v1/models` | ✅ `state` field | ✅ JIT + `/api/v1/models/load` | ✅ load/unload + `lms` | ◐ Bearer, none default | ◐ infer 200 | ✅ type+`max_context_length` | ✅ | `/api/v0/models` w/ `state`,`compatibility_type` |
10
+ | **LM Studio** | 1234 | oai | ✅ `/api/v1/models` (v0 fallback) | ✅ `state` field | ✅ JIT + `/api/v1/models/load` | ✅ load/unload + `lms` | ◐ Bearer, none default | ◐ infer 200 | ✅ type+`max_context_length` | ✅ | `/api/v1/models` (v0 fallback) w/ `state`,`compatibility_type` |
11
11
  | **llama-server** | 8080 | oai | ✅ `/v1/models` | ◐ `/props`,`/slots` (single) | ❌ (1/instance) | ❌ classic | ◐ none / `--api-key` | ✅ `/health` | ◐ ctx via `/props`,`meta` | ✅ | `/props` w/ `default_generation_settings`+`build_info` |
12
12
  | **llama-swap** | 8080 | oai/ant | ✅ `/v1/models` (all config) | ✅ `/running` | ✅ via `model` → restart upstream | ✅ `/api/models/unload`, ttl | ◐ optional multi-scheme | ✅ `/health`→OK | ◐ via upstream | ✅ | `/` → `/ui/`; `/running`,`/upstream/{model}` |
13
13
  | **vLLM** | 8000 | oai | ✅ `/v1/models` | ◐ `/is_sleeping` (dev) | ❌ base · ◐ LoRA | ◐ sleep/wake + LoRA | ◐ none / `--api-key` | ✅ `/health` | ◐ `max_model_len` only | ✅ | `/version` + `/metrics` `vllm:` + `owned_by:"vllm"` |
@@ -38,7 +38,7 @@ adapter registers under (`oai` = `openai-completions`, `ant` = `anthropic-messag
38
38
 
39
39
  1. `GET /` → `Ollama is running` ⇒ Ollama · redirect `/ui/` ⇒ llama-swap
40
40
  2. `GET /api/extra/version` → `{"result":"KoboldCpp"}` ⇒ KoboldCpp
41
- 3. `GET /api/v0/models` 200 w/ `state`/`compatibility_type` ⇒ LM Studio
41
+ 3. `GET /api/v1/models` (v0 fallback) 200 w/ `state`/`compatibility_type` ⇒ LM Studio
42
42
  4. `GET /props` w/ `default_generation_settings`+`build_info` ⇒ llama-server / llamafile
43
43
  5. `GET /version` + `/metrics` `vllm:` ⇒ vLLM
44
44
  6. `GET /v1/models` shape: `owned_by:"vllm"`⇒vLLM · `meta.n_ctx_train`⇒llama.cpp ·
package/package.json CHANGED
@@ -1,11 +1,18 @@
1
1
  {
2
2
  "name": "@hypabolic/crossbar",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "The local/self-hosted inference connector Pi should have shipped with — multi-backend discovery, model switching, and zero-JSON in-TUI onboarding for the Pi coding agent.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "Hypabolic",
8
- "homepage": "https://github.com/hypabolic/crossbar",
8
+ "homepage": "https://github.com/Hypabolic/Crossbar#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/Hypabolic/Crossbar.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/Hypabolic/Crossbar/issues"
15
+ },
9
16
  "keywords": [
10
17
  "pi-package",
11
18
  "pi-extension",
@@ -2,16 +2,20 @@
2
2
  * LM Studio backend adapter.
3
3
  *
4
4
  * Implements the BackendAdapter contract for LM Studio's local server.
5
- * Uses the LM Studio-native /api/v0/* endpoints for discovery and management,
6
- * and delegates inference to the OpenAI-compatible /v1/* layer.
5
+ * Uses the LM Studio-native REST API for discovery and management, and delegates
6
+ * inference to the OpenAI-compatible /v1/* layer.
7
+ *
8
+ * LM Studio 0.4.0+ ships a native `/api/v1/*` REST API (recommended); the older
9
+ * `/api/v0/*` API carries the same rich model fields and is kept as a fallback for
10
+ * pre-0.4.0 servers. We prefer v1 and fall back to v0 only on a 404.
7
11
  *
8
12
  * Key API endpoints:
9
- * GET /api/v0/models — model list with state, type, context lengths
10
- * POST /api/v1/models/load — load a model by id
11
- * POST /api/v1/models/unload — unload a model by id
13
+ * GET /api/v1/models (→ /api/v0/models fallback) — model list with state, type, context length
14
+ * POST /api/v1/models/load — load a model by id
15
+ * POST /api/v1/models/unload — unload a model by id
12
16
  *
13
17
  * Fingerprint discriminator: data[] entries have both `state` and
14
- * `compatibility_type` fields (unique to LM Studio's v0 API).
18
+ * `compatibility_type` fields (unique to LM Studio's native API).
15
19
  */
16
20
 
17
21
  import { Capability } from "../core/capability.ts";
@@ -24,9 +28,14 @@ import type {
24
28
  ModelDescriptor,
25
29
  PiModelEntry,
26
30
  Probe,
31
+ ProbeResult,
27
32
  ServerCredential,
28
33
  } from "../core/types.ts";
29
34
 
35
+ /** Native model-list endpoints, in preference order (v1 first, v0 fallback for <0.4.0). */
36
+ const MODELS_V1 = "/api/v1/models";
37
+ const MODELS_V0 = "/api/v0/models";
38
+
30
39
  // ---------------------------------------------------------------------------
31
40
  // LM Studio API shapes (narrowed from unknown JSON)
32
41
  // ---------------------------------------------------------------------------
@@ -140,10 +149,21 @@ class LmStudioAdapter implements BackendAdapter {
140
149
  Capability.Streaming,
141
150
  ]);
142
151
 
152
+ /**
153
+ * Fetch the native model list, preferring /api/v1/models and falling back to
154
+ * /api/v0/models for LM Studio < 0.4.0 (which only exposes the v0 REST API).
155
+ * Falls back ONLY on a 404 so auth (401) and unreachable (0) errors propagate.
156
+ */
157
+ private async modelsResponse(probe: Probe): Promise<ProbeResult> {
158
+ const v1 = await probe(MODELS_V1);
159
+ if (v1.status === 404) return probe(MODELS_V0);
160
+ return v1;
161
+ }
162
+
143
163
  // --- fingerprint ----------------------------------------------------------
144
164
 
145
165
  async fingerprint(baseUrl: string, probe: Probe): Promise<DiscoveredServer | null> {
146
- const r = await probe("/api/v0/models");
166
+ const r = await this.modelsResponse(probe);
147
167
  if (!r.ok || r.status === 0) return null;
148
168
  if (!hasLmsDiscriminator(r.json)) return null;
149
169
 
@@ -163,7 +183,7 @@ class LmStudioAdapter implements BackendAdapter {
163
183
  _cred: ServerCredential,
164
184
  probe: Probe,
165
185
  ): Promise<HealthStatus> {
166
- const r = await probe("/api/v0/models");
186
+ const r = await this.modelsResponse(probe);
167
187
  if (r.status === 0) return { state: "unreachable" };
168
188
  if (r.status === 401) return { state: "unauthorized" };
169
189
  if (!r.ok) return { state: "degraded" };
@@ -179,7 +199,7 @@ class LmStudioAdapter implements BackendAdapter {
179
199
  _cred: ServerCredential,
180
200
  probe: Probe,
181
201
  ): Promise<ModelDescriptor[]> {
182
- const r = await probe("/api/v0/models");
202
+ const r = await this.modelsResponse(probe);
183
203
  if (!r.ok) {
184
204
  if (r.status === 401) throw new Error("401 Unauthorized");
185
205
  if (r.status === 0) throw new Error("listModels failed: server unreachable");
@@ -197,7 +217,7 @@ class LmStudioAdapter implements BackendAdapter {
197
217
  _cred: ServerCredential,
198
218
  probe: Probe,
199
219
  ): Promise<LoadedState> {
200
- const r = await probe("/api/v0/models");
220
+ const r = await this.modelsResponse(probe);
201
221
  if (!r.ok) {
202
222
  if (r.status === 401) throw new Error("401 Unauthorized");
203
223
  if (r.status === 0) throw new Error("introspectLoaded failed: server unreachable");
@@ -242,7 +262,7 @@ class LmStudioAdapter implements BackendAdapter {
242
262
  }
243
263
 
244
264
  // Step 2: Confirm via model list that the target is now loaded
245
- const r2 = await probe("/api/v0/models");
265
+ const r2 = await this.modelsResponse(probe);
246
266
  if (!r2.ok) {
247
267
  if (r2.status === 0) throw new Error("switchModel confirmation failed: server went down");
248
268
  if (r2.status === 401) throw new Error("401 Unauthorized");