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.
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
@@ -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
- Pi extension that discovers models from a local [OMLX](https://github.com/Open-Model-Lookup-Exchange) server and registers them as a native Pi provider. Switch models with Pi's built-in `/model` command.
3
+ > Seamlessly integrate your local [oMLX](https://github.com/jundot/omlx) models into Pi.
4
4
 
5
- ## Install
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
- ```sh
8
- pi install npm:pi-omlx-picker
9
- ```
10
-
11
- ## Configure
12
-
13
- Set these env vars (or copy `.env-example` to `.env`):
7
+ ![Pi OMLX Picker Demo](./.assets/demo.gif)
14
8
 
15
- ```sh
16
- export OMLX_BASE_URL="http://127.0.0.1:8008/v1"
17
- export OMLX_API_KEY="omlx-..."
18
- ```
9
+ ## ✨ Features
19
10
 
20
- If either is missing, the provider is skipped and Pi logs a message on startup.
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
- Optionally override the model metadata path (default: `~/.omlx/model_settings.json`):
15
+ ## 📦 Installation
23
16
 
24
17
  ```sh
25
- export OMLX_MODEL_SETTINGS_PATH="/path/to/model_settings.json"
18
+ pi install npm:pi-omlx-picker
26
19
  ```
27
20
 
28
- ## How it works
21
+ ## 🚀 Quick Start
29
22
 
30
- On startup, the extension fetches available models from OMLX, merges local `model_settings.json` metadata, and registers an `omlx` provider in Pi. Thinking controls are applied per-request based on each model's `thinkingDefault`.
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
- ## Debugging
27
+ Re-run `/omlx-login` to update credentials.
33
28
 
34
- ```sh
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
- See [docs/DEBUG.md](docs/DEBUG.md) for triage order.
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 = "omlx";
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
- "name": "pi-omlx-picker",
3
- "version": "0.2.0",
4
- "type": "module",
5
- "description": "Pi extension that maps OMLX model settings into native Pi model metadata.",
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
- }
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
@@ -545,4 +545,3 @@ function compactObject(
545
545
  });
546
546
  return entries.length > 0 ? Object.fromEntries(entries) : undefined;
547
547
  }
548
-
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
+ }