pi-omlx-picker 0.2.0 → 0.2.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/CONTRIBUTING.md +52 -0
- package/README.md +2 -24
- 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/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
|
@@ -10,32 +10,10 @@ pi install npm:pi-omlx-picker
|
|
|
10
10
|
|
|
11
11
|
## Configure
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Run `/omlx-login` in Pi and paste your OMLX base URL and API key. That's it. Re-run `/omlx-login` to change credentials.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
export OMLX_BASE_URL="http://127.0.0.1:8008/v1"
|
|
17
|
-
export OMLX_API_KEY="omlx-..."
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
If either is missing, the provider is skipped and Pi logs a message on startup.
|
|
21
|
-
|
|
22
|
-
Optionally override the model metadata path (default: `~/.omlx/model_settings.json`):
|
|
23
|
-
|
|
24
|
-
```sh
|
|
25
|
-
export OMLX_MODEL_SETTINGS_PATH="/path/to/model_settings.json"
|
|
26
|
-
```
|
|
15
|
+
Env-var overrides, model metadata overlay, and stream timeout knobs are documented in [docs/CONFIGURATION.md](./docs/CONFIGURATION.md).
|
|
27
16
|
|
|
28
17
|
## How it works
|
|
29
18
|
|
|
30
19
|
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`.
|
|
31
|
-
|
|
32
|
-
## Debugging
|
|
33
|
-
|
|
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
|
-
```
|
|
40
|
-
|
|
41
|
-
See [docs/DEBUG.md](docs/DEBUG.md) for triage order.
|
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.1",
|
|
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
|
+
}
|