pi-omlx-picker 0.2.0 → 0.2.2
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/.assets/demo.gif +0 -0
- package/.assets/demo.tape +31 -0
- package/CONTRIBUTING.md +52 -0
- package/README.md +16 -26
- package/index.ts +41 -2
- package/package.json +23 -23
- package/src/auth-storage.ts +41 -0
- package/src/catalog.ts +0 -1
- package/src/config.ts +21 -0
package/.assets/demo.gif
ADDED
|
Binary file
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Output demo.gif
|
|
2
|
+
|
|
3
|
+
# Visual Settings
|
|
4
|
+
Set FontSize 15
|
|
5
|
+
Set Width 900
|
|
6
|
+
Set Height 500
|
|
7
|
+
Set Padding 20
|
|
8
|
+
Set Theme "Catppuccin Mocha"
|
|
9
|
+
Set WindowBar Colorful
|
|
10
|
+
|
|
11
|
+
Sleep 1s
|
|
12
|
+
|
|
13
|
+
Type "pi"
|
|
14
|
+
Enter
|
|
15
|
+
Sleep 1.5s
|
|
16
|
+
|
|
17
|
+
Type "/mo"
|
|
18
|
+
Sleep 1.5s
|
|
19
|
+
|
|
20
|
+
Enter
|
|
21
|
+
Sleep 1.5s
|
|
22
|
+
|
|
23
|
+
Down
|
|
24
|
+
Sleep 500ms
|
|
25
|
+
Down
|
|
26
|
+
Sleep 500ms
|
|
27
|
+
Up
|
|
28
|
+
Sleep 500ms
|
|
29
|
+
|
|
30
|
+
Enter
|
|
31
|
+
Sleep 3s
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Local development for `pi-omlx-picker`. User config: [docs/CONFIGURATION.md](./docs/CONFIGURATION.md).
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install
|
|
9
|
+
mise run verify
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
A live OMLX server is needed for `mise run smoke:omlx` and `mise run verify:live`.
|
|
13
|
+
|
|
14
|
+
## Tasks
|
|
15
|
+
|
|
16
|
+
Run via [`mise`](https://mise.jdx.dev/):
|
|
17
|
+
|
|
18
|
+
- `mise run verify` — Biome, TypeScript type checking, unit tests.
|
|
19
|
+
- `mise run smoke:omlx` — live OMLX probe: reasoning model, non-thinking model, tool-flow request.
|
|
20
|
+
- `mise run verify:live` — typecheck, unit tests, and live smoke (skips Biome; run `mise run verify` first if you want lint coverage).
|
|
21
|
+
- `mise run debug:omlx` — OMLX config, model path, template, log, and cache diagnostics. Pass model names after `--` to narrow, e.g. `mise run debug:omlx -- opus sonnet`.
|
|
22
|
+
- `mise run debug:pi` — Pi install, config, session, log, and cache diagnostics. Pass `install`, `config`, `sessions`, `logs`, or `cache` after `--`.
|
|
23
|
+
- `mise run debug:pi -- timeline <session-id|session-file|iso-time>` — Pi provider + OMLX server + changed-files window around a stuck turn. Use `--minutes=N` to set the window (default 3).
|
|
24
|
+
|
|
25
|
+
## Triage order
|
|
26
|
+
|
|
27
|
+
1. Read the latest provider events in `log/provider-debug.log` (or `~/.pi/packages/pi-omlx-picker/log/provider-debug.log` when installed via Pi). Look for `stream_first_delta_timeout`, `assistant_stop_diagnosis`, and the most recent `before_provider_request` / `after_provider_response` pair. The latest `mise run smoke:omlx` proof lives in `log/smoke-test/<iso-timestamp>.json`.
|
|
28
|
+
2. Inspect Pi host state: `mise run debug:pi`.
|
|
29
|
+
3. Inspect OMLX config and model files: `mise run debug:omlx`.
|
|
30
|
+
4. Run live smoke: `mise run smoke:omlx`.
|
|
31
|
+
5. Check upstream OMLX repo releases and issues before writing local workarounds.
|
|
32
|
+
6. Only then change code.
|
|
33
|
+
|
|
34
|
+
Most fixes are upstream (OMLX `model_settings.json`, `chat_template.jinja`), not in Pi-side code. New-session-OK plus takeover-broken means session state, not model capacity.
|
|
35
|
+
|
|
36
|
+
## Failure families
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
symptom
|
|
40
|
+
├─ package didn't load, wrong install, stale session, compaction, takeover
|
|
41
|
+
│ └─ mise run debug:pi
|
|
42
|
+
├─ model alias, model settings, template, rope config, OMLX/HF cache
|
|
43
|
+
│ └─ mise run debug:omlx
|
|
44
|
+
├─ live model request behavior
|
|
45
|
+
│ └─ mise run smoke:omlx
|
|
46
|
+
├─ new session good, takeover bad
|
|
47
|
+
│ └─ mise run debug:pi -- sessions, then compare model_settings.json and chat_template.jinja
|
|
48
|
+
├─ local checks pass but live smoke fails
|
|
49
|
+
│ └─ check upstream OMLX repo, releases, and issues
|
|
50
|
+
└─ model not appearing in Pi /model list
|
|
51
|
+
└─ check OMLX_BASE_URL is reachable, then mise run debug:omlx
|
|
52
|
+
```
|
package/README.md
CHANGED
|
@@ -1,41 +1,31 @@
|
|
|
1
1
|
# Pi OMLX Picker
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Seamlessly integrate your local [oMLX](https://github.com/jundot/omlx) models into Pi.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This extension discovers models from a local OMLX server and registers them as native Pi providers. Switch between your local and remote models effortlessly using Pi's built-in `/model` command.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
pi install npm:pi-omlx-picker
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Configure
|
|
12
|
-
|
|
13
|
-
Set these env vars (or copy `.env-example` to `.env`):
|
|
7
|
+

|
|
14
8
|
|
|
15
|
-
|
|
16
|
-
export OMLX_BASE_URL="http://127.0.0.1:8008/v1"
|
|
17
|
-
export OMLX_API_KEY="omlx-..."
|
|
18
|
-
```
|
|
9
|
+
## ✨ Features
|
|
19
10
|
|
|
20
|
-
|
|
11
|
+
* **Zero-Friction Discovery:** Automatically fetches and registers available OMLX models on startup.
|
|
12
|
+
* **Native Integration:** Models show up in the standard `/model` menu—no custom commands needed for chat.
|
|
13
|
+
* **Smart Overrides:** Applies per-request thinking controls based on each model's `thinkingDefault` metadata.
|
|
21
14
|
|
|
22
|
-
|
|
15
|
+
## 📦 Installation
|
|
23
16
|
|
|
24
17
|
```sh
|
|
25
|
-
|
|
18
|
+
pi install npm:pi-omlx-picker
|
|
26
19
|
```
|
|
27
20
|
|
|
28
|
-
##
|
|
21
|
+
## 🚀 Quick Start
|
|
29
22
|
|
|
30
|
-
|
|
23
|
+
1. Run `/omlx-login` in Pi.
|
|
24
|
+
2. Paste your OMLX base URL and API key when prompted.
|
|
25
|
+
3. Type `/model` to see and select your OMLX models.
|
|
31
26
|
|
|
32
|
-
|
|
27
|
+
Re-run `/omlx-login` to update credentials.
|
|
33
28
|
|
|
34
|
-
|
|
35
|
-
mise run debug:omlx # OMLX config, model path, template, cache
|
|
36
|
-
mise run debug:pi # Pi install, config, session, log, cache
|
|
37
|
-
mise run verify # Biome + typecheck + unit tests
|
|
38
|
-
mise run smoke:omlx # Live smoke test against a running OMLX server
|
|
39
|
-
```
|
|
29
|
+
## ⚙️ Configuration
|
|
40
30
|
|
|
41
|
-
|
|
31
|
+
Env-var overrides, model metadata overlay, and stream timeout knobs are documented in [docs/CONFIGURATION.md](./docs/CONFIGURATION.md).
|
package/index.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { PROVIDER_KEY, saveOmlxCredential } from "./src/auth-storage.ts";
|
|
2
3
|
import { fetchModels, resolveLocalModelSettingsPath, type OmlxModel } from "./src/catalog.ts";
|
|
3
|
-
import { loadConfig, MissingEnvError, type OmlxConfig } from "./src/config.ts";
|
|
4
|
+
import { applyStoredCredentialToEnv, loadConfig, MissingEnvError, normalizeBaseUrl, type OmlxConfig } from "./src/config.ts";
|
|
4
5
|
import { toProviderConfig } from "./src/provider.ts";
|
|
5
6
|
import { applyOmlxThinkingControls } from "./src/thinking.ts";
|
|
6
7
|
|
|
7
|
-
const PROVIDER =
|
|
8
|
+
const PROVIDER = PROVIDER_KEY;
|
|
8
9
|
const EXTENSION_SINGLETON_KEY = Symbol.for("pi-omlx-picker/loaded");
|
|
9
10
|
|
|
10
11
|
interface State {
|
|
@@ -30,6 +31,7 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
30
31
|
modelSettingsPath: undefined,
|
|
31
32
|
};
|
|
32
33
|
|
|
34
|
+
applyStoredCredentialToEnv();
|
|
33
35
|
await refreshProvider(pi, state);
|
|
34
36
|
|
|
35
37
|
pi.on("before_provider_request", (event, ctx) => {
|
|
@@ -37,8 +39,45 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
37
39
|
const activeModel = findCatalogModel(state, ctx.model.id);
|
|
38
40
|
return applyOmlxThinkingControls(event.payload, pi.getThinkingLevel(), activeModel?.thinkingDefault);
|
|
39
41
|
});
|
|
42
|
+
|
|
43
|
+
pi.registerCommand("omlx-login", {
|
|
44
|
+
description: "Sign in to OMLX (set base URL and API key)",
|
|
45
|
+
handler: async (_args, ctx) => {
|
|
46
|
+
const baseUrlInput = await ctx.ui.input("OMLX base URL", "http://127.0.0.1:8000/v1");
|
|
47
|
+
if (!baseUrlInput) return;
|
|
48
|
+
const apiKey = await ctx.ui.input("OMLX API key", "omlx-...");
|
|
49
|
+
if (!apiKey) return;
|
|
50
|
+
|
|
51
|
+
let baseUrl: string;
|
|
52
|
+
try {
|
|
53
|
+
baseUrl = normalizeBaseUrl(baseUrlInput);
|
|
54
|
+
} catch (err) {
|
|
55
|
+
ctx.ui.notify(`Invalid base URL: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
ctx.ui.notify("Validating OMLX credentials…", "info");
|
|
60
|
+
try {
|
|
61
|
+
await fetchModels(baseUrl, apiKey, { timeoutMs: VALIDATE_TIMEOUT_MS });
|
|
62
|
+
} catch (err) {
|
|
63
|
+
ctx.ui.notify(`OMLX login failed: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
saveOmlxCredential(baseUrl, apiKey);
|
|
68
|
+
process.env.OMLX_BASE_URL = baseUrl;
|
|
69
|
+
process.env.OMLX_API_KEY = apiKey;
|
|
70
|
+
await refreshProvider(pi, state);
|
|
71
|
+
const message = state.registered
|
|
72
|
+
? `OMLX connected — ${state.catalog.length} models available`
|
|
73
|
+
: `OMLX login saved but provider failed: ${state.lastError ?? "unknown error"}`;
|
|
74
|
+
ctx.ui.notify(message, state.registered ? "info" : "warning");
|
|
75
|
+
},
|
|
76
|
+
});
|
|
40
77
|
}
|
|
41
78
|
|
|
79
|
+
const VALIDATE_TIMEOUT_MS = 10_000;
|
|
80
|
+
|
|
42
81
|
function clearRegisteredProvider(pi: ExtensionAPI, state: State): void {
|
|
43
82
|
if (state.registered) {
|
|
44
83
|
pi.unregisterProvider(PROVIDER);
|
package/package.json
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
2
|
+
"name": "pi-omlx-picker",
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Pi extension that discovers models from a local OMLX server and registers them as a native Pi provider.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "node --import tsx --test 'test/*.test.ts'",
|
|
9
|
+
"debug:omlx": "tsx scripts/debug-omlx.ts",
|
|
10
|
+
"debug:pi": "tsx scripts/debug-pi.ts",
|
|
11
|
+
"smoke:omlx": "tsx scripts/smoke-live-omlx.ts",
|
|
12
|
+
"typecheck": "tsc --noEmit"
|
|
13
|
+
},
|
|
14
|
+
"pi": {
|
|
15
|
+
"extensions": [
|
|
16
|
+
"./index.ts"
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@mariozechner/pi-ai": "^0.73.1",
|
|
21
|
+
"@mariozechner/pi-coding-agent": "*",
|
|
22
|
+
"tsx": "^4.7.0",
|
|
23
|
+
"typescript": "^6.0.3"
|
|
24
|
+
}
|
|
25
25
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ApiKeyCredential,
|
|
3
|
+
AuthStorage,
|
|
4
|
+
} from "@mariozechner/pi-coding-agent";
|
|
5
|
+
|
|
6
|
+
export const PROVIDER_KEY = "omlx";
|
|
7
|
+
|
|
8
|
+
export interface OmlxStoredCredential {
|
|
9
|
+
baseUrl: string;
|
|
10
|
+
apiKey: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type OmlxApiKeyCredential = ApiKeyCredential & { baseUrl?: string };
|
|
14
|
+
|
|
15
|
+
let storage: AuthStorage | undefined;
|
|
16
|
+
function getStorage(): AuthStorage {
|
|
17
|
+
if (!storage) storage = AuthStorage.create();
|
|
18
|
+
return storage;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function _setStorageForTesting(s: AuthStorage | undefined): void {
|
|
22
|
+
storage = s;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function loadOmlxCredential(): OmlxStoredCredential | undefined {
|
|
26
|
+
const cred = getStorage().get(PROVIDER_KEY) as
|
|
27
|
+
| OmlxApiKeyCredential
|
|
28
|
+
| undefined;
|
|
29
|
+
if (!cred || cred.type !== "api_key") return undefined;
|
|
30
|
+
if (!cred.baseUrl || !cred.key) return undefined;
|
|
31
|
+
return { baseUrl: cred.baseUrl, apiKey: cred.key };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function saveOmlxCredential(baseUrl: string, apiKey: string): void {
|
|
35
|
+
const cred: OmlxApiKeyCredential = { type: "api_key", key: apiKey, baseUrl };
|
|
36
|
+
getStorage().set(PROVIDER_KEY, cred);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function deleteOmlxCredential(): void {
|
|
40
|
+
getStorage().remove(PROVIDER_KEY);
|
|
41
|
+
}
|
package/src/catalog.ts
CHANGED
package/src/config.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { loadOmlxCredential } from "./auth-storage.ts";
|
|
2
|
+
|
|
1
3
|
export interface OmlxConfig {
|
|
2
4
|
apiRoot: string;
|
|
3
5
|
apiKeyEnvVar: string;
|
|
@@ -25,3 +27,22 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): OmlxConfig {
|
|
|
25
27
|
apiKeyEnvVar: "OMLX_API_KEY",
|
|
26
28
|
};
|
|
27
29
|
}
|
|
30
|
+
|
|
31
|
+
// Env vars win over stored creds so CI and per-shell overrides work.
|
|
32
|
+
export function applyStoredCredentialToEnv(
|
|
33
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
34
|
+
): boolean {
|
|
35
|
+
if (env.OMLX_BASE_URL && env.OMLX_API_KEY) return false;
|
|
36
|
+
const stored = loadOmlxCredential();
|
|
37
|
+
if (!stored) return false;
|
|
38
|
+
let applied = false;
|
|
39
|
+
if (!env.OMLX_BASE_URL) {
|
|
40
|
+
env.OMLX_BASE_URL = stored.baseUrl;
|
|
41
|
+
applied = true;
|
|
42
|
+
}
|
|
43
|
+
if (!env.OMLX_API_KEY) {
|
|
44
|
+
env.OMLX_API_KEY = stored.apiKey;
|
|
45
|
+
applied = true;
|
|
46
|
+
}
|
|
47
|
+
return applied;
|
|
48
|
+
}
|