pi-openmodel-provider 0.2.18 → 0.2.19
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/.agents/skills/pi-openmodel-info/SKILL.md +25 -3
- package/CHANGELOG.md +15 -0
- package/index.ts +30 -60
- package/package.json +1 -1
- package/src/auth/login.ts +17 -0
- package/src/formatters/stability.ts +26 -1
- package/src/formatters/status.ts +46 -0
|
@@ -32,9 +32,31 @@ Models are cached locally at `~/.pi/agent/cache/openmodel-models.json` with a **
|
|
|
32
32
|
|
|
33
33
|
## Thinking levels
|
|
34
34
|
|
|
35
|
-
Reasoning models support thinking levels:
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
Reasoning models support thinking levels mapped per protocol:
|
|
36
|
+
|
|
37
|
+
**Messages protocol (`anthropic-messages`):**
|
|
38
|
+
|
|
39
|
+
| Level | Mapped value |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `off` | `null` |
|
|
42
|
+
| `minimal` | `low` |
|
|
43
|
+
| `low` | `medium` |
|
|
44
|
+
| `medium` | `high` |
|
|
45
|
+
| `high` | `high` |
|
|
46
|
+
| `xhigh` | `max` |
|
|
47
|
+
|
|
48
|
+
**Responses protocol (`openai-responses`):** uses `reasoning_effort`
|
|
49
|
+
|
|
50
|
+
| Level | Mapped value |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `off` | `null` |
|
|
53
|
+
| `minimal` | `low` |
|
|
54
|
+
| `low` | `low` |
|
|
55
|
+
| `medium` | `medium` |
|
|
56
|
+
| `high` | `high` |
|
|
57
|
+
| `xhigh` | `high` |
|
|
58
|
+
|
|
59
|
+
**Gemini protocol (`google-generative-ai`):** no thinking level mapping (returns empty).
|
|
38
60
|
|
|
39
61
|
## Compat flags
|
|
40
62
|
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.2.19] - 2026-06-27
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `src/auth/login.ts` — `hasApiKey()` function to check for configured credentials (was inline in `index.ts`)
|
|
12
|
+
- `src/formatters/status.ts` — new module with pure `formatProviderStatus()` for the `/openmodel` command display
|
|
13
|
+
- `formatStabilityDetail()` and `formatStabilitySummaryLine()` in `src/formatters/stability.ts` — extract stability formatting from `index.ts`
|
|
14
|
+
- `CommandContext` interface in `index.ts` — types the command handler context instead of using `any`
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- `index.ts` — refactored to pure orchestration: `/openmodel` and `/openmodel-stability` handlers delegate I/O, formatting, and display logic to extracted functions
|
|
18
|
+
- `.agents/skills/pi-openmodel-info/SKILL.md` — completed thinking levels section with full mappings (`minimal` through `xhigh`) for both protocols
|
|
19
|
+
|
|
20
|
+
### Removed
|
|
21
|
+
- `import { readFile } from "node:fs/promises"` and `import { homedir } from "node:os"` from `index.ts` (moved into `hasApiKey()`)
|
|
22
|
+
|
|
8
23
|
## [0.2.18] - 2026-06-26
|
|
9
24
|
|
|
10
25
|
### Added
|
package/index.ts
CHANGED
|
@@ -6,15 +6,25 @@
|
|
|
6
6
|
|
|
7
7
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"
|
|
8
8
|
import { fetchOpenModelModels } from "./src/api/models.ts"
|
|
9
|
-
import { login, refreshToken, getApiKey } from "./src/auth/login.ts"
|
|
9
|
+
import { login, refreshToken, getApiKey, hasApiKey } from "./src/auth/login.ts"
|
|
10
10
|
import {
|
|
11
11
|
fetchModelStabilitySummary,
|
|
12
12
|
fetchModelStabilityDetail,
|
|
13
13
|
} from "./src/api/stability.ts"
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
formatStabilityDetail,
|
|
16
|
+
formatStabilitySummaryLine,
|
|
17
|
+
} from "./src/formatters/stability.ts"
|
|
18
|
+
import { formatProviderStatus } from "./src/formatters/status.ts"
|
|
15
19
|
import { readModelCache, writeModelCache } from "./src/cache.ts"
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
|
|
21
|
+
/** Minimal command context type for pi extension command handlers. */
|
|
22
|
+
interface CommandContext {
|
|
23
|
+
signal?: AbortSignal
|
|
24
|
+
ui: {
|
|
25
|
+
notify(message: string, type: string): void
|
|
26
|
+
}
|
|
27
|
+
}
|
|
18
28
|
|
|
19
29
|
export default async function (pi: ExtensionAPI) {
|
|
20
30
|
let models: Awaited<ReturnType<typeof fetchOpenModelModels>> = []
|
|
@@ -75,70 +85,31 @@ export default async function (pi: ExtensionAPI) {
|
|
|
75
85
|
// /openmodel - Show provider status
|
|
76
86
|
pi.registerCommand("openmodel", {
|
|
77
87
|
description: "Show OpenModel provider status",
|
|
78
|
-
handler: async (_args: string, ctx:
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
} catch {
|
|
89
|
-
// Auth file not found
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const lines = [
|
|
93
|
-
"╔══════════════════════════════════╗",
|
|
94
|
-
"║ OpenModel.ai ║",
|
|
95
|
-
"╠══════════════════════════════════╣",
|
|
96
|
-
`║ Models: ${String(count).padStart(3)} loaded${fromCache ? " (cached)" : ""} ║`,
|
|
97
|
-
hasApiKey ? "║ API Key: ✅ Configured ║" : "║ API Key: ❌ Not configured ║",
|
|
98
|
-
"╠══════════════════════════════════╣",
|
|
99
|
-
"║ Commands: ║",
|
|
100
|
-
"║ /model openmodel/... ║",
|
|
101
|
-
"║ /openmodel-stability ║",
|
|
102
|
-
"╚══════════════════════════════════╝",
|
|
103
|
-
]
|
|
104
|
-
|
|
105
|
-
const hints: string[] = []
|
|
106
|
-
if (!hasApiKey) {
|
|
107
|
-
hints.push("ℹ️ Run /login → OpenModel → paste your API key")
|
|
108
|
-
}
|
|
109
|
-
if (count === 0 && hasApiKey) {
|
|
110
|
-
hints.push("ℹ️ Run /reload after setting your API key")
|
|
111
|
-
}
|
|
112
|
-
if (count === 0 && modelError) {
|
|
113
|
-
hints.push(`ℹ️ ${modelError}`)
|
|
114
|
-
}
|
|
115
|
-
hints.push("ℹ️ Press Ctrl+L to select a model")
|
|
116
|
-
|
|
117
|
-
ctx.ui.notify([...lines, ...hints].join("\n"), "info")
|
|
88
|
+
handler: async (_args: string, ctx: CommandContext) => {
|
|
89
|
+
ctx.ui.notify(
|
|
90
|
+
formatProviderStatus({
|
|
91
|
+
count: models.length,
|
|
92
|
+
fromCache,
|
|
93
|
+
hasApiKey: await hasApiKey(),
|
|
94
|
+
modelError,
|
|
95
|
+
}),
|
|
96
|
+
"info",
|
|
97
|
+
)
|
|
118
98
|
},
|
|
119
99
|
})
|
|
120
100
|
|
|
121
101
|
// /openmodel-stability - Show model health metrics
|
|
122
102
|
pi.registerCommand("openmodel-stability", {
|
|
123
103
|
description: "Show model stability metrics (24h)",
|
|
124
|
-
handler: async (args: string | undefined, ctx:
|
|
104
|
+
handler: async (args: string | undefined, ctx: CommandContext) => {
|
|
125
105
|
try {
|
|
106
|
+
const fetchOptions = ctx.signal ? { signal: ctx.signal } : {}
|
|
126
107
|
if (args?.trim()) {
|
|
127
108
|
const name = args.trim()
|
|
128
|
-
const detail = await fetchModelStabilityDetail(name,
|
|
129
|
-
|
|
130
|
-
`📊 ${detail.model_name}`,
|
|
131
|
-
`━━━━━━━━━━━━━━━━━━━━━━`,
|
|
132
|
-
`Health: ${formatHealthStatus(detail.health_status)}`,
|
|
133
|
-
`Success: ${detail.summary.success_rate.toFixed(2)}%`,
|
|
134
|
-
`Latency: ${detail.summary.avg_latency_ms.toFixed(0)}ms`,
|
|
135
|
-
`TTFT: ${detail.summary.avg_ttft_ms.toFixed(0)}ms`,
|
|
136
|
-
`Throughput: ${detail.summary.avg_tps.toFixed(1)} t/s`,
|
|
137
|
-
`Confidence: ${detail.confidence}`,
|
|
138
|
-
]
|
|
139
|
-
ctx.ui.notify(lines.join("\n"), "info")
|
|
109
|
+
const detail = await fetchModelStabilityDetail(name, fetchOptions)
|
|
110
|
+
ctx.ui.notify(formatStabilityDetail(detail), "info")
|
|
140
111
|
} else {
|
|
141
|
-
const summary = await fetchModelStabilitySummary(
|
|
112
|
+
const summary = await fetchModelStabilitySummary(fetchOptions)
|
|
142
113
|
if (summary.length === 0) {
|
|
143
114
|
ctx.ui.notify("📊 No stability data available for any model yet.", "warning")
|
|
144
115
|
return
|
|
@@ -149,8 +120,7 @@ export default async function (pi: ExtensionAPI) {
|
|
|
149
120
|
return (order[a.health_status] ?? 5) - (order[b.health_status] ?? 5)
|
|
150
121
|
})
|
|
151
122
|
for (const s of sorted) {
|
|
152
|
-
|
|
153
|
-
lines.push(`${emoji} ${s.model_name.padEnd(28)} ${s.success_rate.toFixed(1).padStart(5)}% ${s.avg_latency_ms.toFixed(0).padStart(5)}ms ${s.avg_tps.toFixed(1).padStart(6)} t/s`)
|
|
123
|
+
lines.push(formatStabilitySummaryLine(s))
|
|
154
124
|
}
|
|
155
125
|
ctx.ui.notify(lines.join("\n"), "info")
|
|
156
126
|
}
|
package/package.json
CHANGED
package/src/auth/login.ts
CHANGED
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
* Since OpenModel API keys don't expire, "refresh" is a no-op.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import { readFile } from "node:fs/promises"
|
|
14
|
+
import { join } from "node:path"
|
|
15
|
+
import { homedir } from "node:os"
|
|
13
16
|
import { sanitizeApiKey, isValidApiKey } from "./validate.ts"
|
|
14
17
|
|
|
15
18
|
export interface OAuthLoginCallbacks {
|
|
@@ -130,3 +133,17 @@ export async function refreshToken(
|
|
|
130
133
|
export function getApiKey(credentials: OAuthCredentials): string {
|
|
131
134
|
return credentials.access
|
|
132
135
|
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Check if the user has configured an OpenModel API key in pi's auth file.
|
|
139
|
+
*/
|
|
140
|
+
export async function hasApiKey(): Promise<boolean> {
|
|
141
|
+
const authPath = join(homedir(), ".pi", "agent", "auth.json")
|
|
142
|
+
try {
|
|
143
|
+
const content = await readFile(authPath, "utf-8")
|
|
144
|
+
const data = JSON.parse(content)
|
|
145
|
+
return !!(data.openmodel?.access || data.openmodel?.refresh)
|
|
146
|
+
} catch {
|
|
147
|
+
return false
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -6,7 +6,12 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { HealthStatus } from "../health.ts"
|
|
9
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
ConfidenceLevel,
|
|
11
|
+
ModelStability,
|
|
12
|
+
ModelStabilityDetail,
|
|
13
|
+
} from "../api/stability.ts"
|
|
14
|
+
|
|
10
15
|
/** Format health status with emoji */
|
|
11
16
|
export function formatHealthStatus(status: HealthStatus): string {
|
|
12
17
|
switch (status) {
|
|
@@ -34,3 +39,23 @@ export function formatConfidence(level: ConfidenceLevel): string {
|
|
|
34
39
|
return "⚪ Low"
|
|
35
40
|
}
|
|
36
41
|
}
|
|
42
|
+
|
|
43
|
+
/** Format detailed stability view for a single model */
|
|
44
|
+
export function formatStabilityDetail(detail: ModelStabilityDetail): string {
|
|
45
|
+
return [
|
|
46
|
+
`📊 ${detail.model_name}`,
|
|
47
|
+
`━━━━━━━━━━━━━━━━━━━━━━`,
|
|
48
|
+
`Health: ${formatHealthStatus(detail.health_status)}`,
|
|
49
|
+
`Success: ${detail.summary.success_rate.toFixed(2)}%`,
|
|
50
|
+
`Latency: ${detail.summary.avg_latency_ms.toFixed(0)}ms`,
|
|
51
|
+
`TTFT: ${detail.summary.avg_ttft_ms.toFixed(0)}ms`,
|
|
52
|
+
`Throughput: ${detail.summary.avg_tps.toFixed(1)} t/s`,
|
|
53
|
+
`Confidence: ${formatConfidence(detail.confidence)}`,
|
|
54
|
+
].join("\n")
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Format a single summary line for the stability list */
|
|
58
|
+
export function formatStabilitySummaryLine(s: ModelStability): string {
|
|
59
|
+
const emoji = formatHealthStatus(s.health_status).split(" ")[0]
|
|
60
|
+
return `${emoji} ${s.model_name.padEnd(28)} ${s.success_rate.toFixed(1).padStart(5)}% ${s.avg_latency_ms.toFixed(0).padStart(5)}ms ${s.avg_tps.toFixed(1).padStart(6)} t/s`
|
|
61
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formatters for the /openmodel provider status display.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions — no side effects, no network calls.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface ProviderStatusOptions {
|
|
8
|
+
count: number
|
|
9
|
+
fromCache: boolean
|
|
10
|
+
hasApiKey: boolean
|
|
11
|
+
modelError: string | null
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Format the full /openmodel status display including hints.
|
|
16
|
+
*/
|
|
17
|
+
export function formatProviderStatus(options: ProviderStatusOptions): string {
|
|
18
|
+
const { count, fromCache, hasApiKey, modelError } = options
|
|
19
|
+
|
|
20
|
+
const lines = [
|
|
21
|
+
"╔══════════════════════════════════╗",
|
|
22
|
+
"║ OpenModel.ai ║",
|
|
23
|
+
"╠══════════════════════════════════╣",
|
|
24
|
+
`║ Models: ${String(count).padStart(3)} loaded${fromCache ? " (cached)" : ""} ║`,
|
|
25
|
+
hasApiKey ? "║ API Key: ✅ Configured ║" : "║ API Key: ❌ Not configured ║",
|
|
26
|
+
"╠══════════════════════════════════╣",
|
|
27
|
+
"║ Commands: ║",
|
|
28
|
+
"║ /model openmodel/... ║",
|
|
29
|
+
"║ /openmodel-stability ║",
|
|
30
|
+
"╚══════════════════════════════════╝",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
const hints: string[] = []
|
|
34
|
+
if (!hasApiKey) {
|
|
35
|
+
hints.push("ℹ️ Run /login → OpenModel → paste your API key")
|
|
36
|
+
}
|
|
37
|
+
if (count === 0 && hasApiKey) {
|
|
38
|
+
hints.push("ℹ️ Run /reload after setting your API key")
|
|
39
|
+
}
|
|
40
|
+
if (count === 0 && modelError) {
|
|
41
|
+
hints.push(`ℹ️ ${modelError}`)
|
|
42
|
+
}
|
|
43
|
+
hints.push("ℹ️ Press Ctrl+L to select a model")
|
|
44
|
+
|
|
45
|
+
return [...lines, ...hints].join("\n")
|
|
46
|
+
}
|