@2tle/pi-provider-manager 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.
- package/README.md +18 -0
- package/index.ts +55 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,6 +86,24 @@ The extension targets OpenAI Chat Completions-compatible services that provide a
|
|
|
86
86
|
|
|
87
87
|
Provider implementations differ in support for developer roles, reasoning options, token fields, and streaming usage. This extension currently uses Pi's standard `openai-completions` transport.
|
|
88
88
|
|
|
89
|
+
### Thinking / reasoning
|
|
90
|
+
|
|
91
|
+
During each catalog refresh, the extension recognizes OpenAI-compatible reasoning metadata from either top-level fields or `capabilities`:
|
|
92
|
+
|
|
93
|
+
- `supports_reasoning`, `supports_reasoning_effort`, or `capabilities.supports_reasoning`
|
|
94
|
+
- `reasoning_efforts` or `capabilities.reasoning_effort` (an array of strings or `{ "value": "..." }` objects)
|
|
95
|
+
|
|
96
|
+
Reasoning-capable models are registered with Pi's `reasoning_effort` compatibility enabled. Supported effort values are exposed as Pi thinking levels; unavailable levels are hidden. An upstream `ultra` effort is mapped to Pi's highest available level, `max`.
|
|
97
|
+
|
|
98
|
+
For example, after `/provider reload opencodex`, select a discovered reasoning model with a thinking suffix:
|
|
99
|
+
|
|
100
|
+
```text
|
|
101
|
+
/provider reload opencodex
|
|
102
|
+
# Then select: opencodex/gpt-5.6-sol:max
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
An endpoint must actually accept the OpenAI Chat Completions `reasoning_effort` request field. Providers that use a different thinking protocol (for example, Qwen's `enable_thinking`) are outside this extension's OpenAI-compatible transport scope.
|
|
106
|
+
|
|
89
107
|
## Stored configuration
|
|
90
108
|
|
|
91
109
|
Provider metadata and API keys are stored separately:
|
package/index.ts
CHANGED
|
@@ -11,6 +11,8 @@ const API = "openai-completions" as const;
|
|
|
11
11
|
const DEFAULT_CONTEXT_WINDOW = 128_000;
|
|
12
12
|
const DEFAULT_MAX_TOKENS = 16_384;
|
|
13
13
|
const REFRESH_TIMEOUT_MS = 30_000;
|
|
14
|
+
const PI_THINKING_LEVELS = ["minimal", "low", "medium", "high", "xhigh", "max"] as const;
|
|
15
|
+
type PiThinkingLevel = (typeof PI_THINKING_LEVELS)[number];
|
|
14
16
|
|
|
15
17
|
interface StoredProvider {
|
|
16
18
|
id: string;
|
|
@@ -31,6 +33,10 @@ interface ProviderModelConfig {
|
|
|
31
33
|
name: string;
|
|
32
34
|
api: typeof API;
|
|
33
35
|
reasoning: boolean;
|
|
36
|
+
thinkingLevelMap?: Partial<Record<PiThinkingLevel, string | null>>;
|
|
37
|
+
compat?: {
|
|
38
|
+
supportsReasoningEffort: boolean;
|
|
39
|
+
};
|
|
34
40
|
input: ("text" | "image")[];
|
|
35
41
|
cost: {
|
|
36
42
|
input: number;
|
|
@@ -60,8 +66,11 @@ interface OpenAIModelPayload {
|
|
|
60
66
|
maxTokens?: unknown;
|
|
61
67
|
reasoning?: unknown;
|
|
62
68
|
supports_reasoning?: unknown;
|
|
69
|
+
supports_reasoning_effort?: unknown;
|
|
70
|
+
reasoning_efforts?: unknown;
|
|
63
71
|
input?: unknown;
|
|
64
72
|
cost?: unknown;
|
|
73
|
+
capabilities?: unknown;
|
|
65
74
|
}
|
|
66
75
|
|
|
67
76
|
interface OpenAIModelsPayload {
|
|
@@ -80,6 +89,27 @@ function asPositiveNumber(value: unknown, fallback: number): number {
|
|
|
80
89
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
81
90
|
}
|
|
82
91
|
|
|
92
|
+
function getReasoningEfforts(value: unknown): Set<string> {
|
|
93
|
+
if (!Array.isArray(value)) return new Set();
|
|
94
|
+
return new Set(value.flatMap((item) => {
|
|
95
|
+
if (typeof item === "string") return [item.toLowerCase()];
|
|
96
|
+
const effort = asNonEmptyString(asRecord(item)?.value);
|
|
97
|
+
return effort ? [effort.toLowerCase()] : [];
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function thinkingLevelMap(efforts: Set<string>): Partial<Record<PiThinkingLevel, string | null>> | undefined {
|
|
102
|
+
if (efforts.size === 0) return undefined;
|
|
103
|
+
|
|
104
|
+
const map: Partial<Record<PiThinkingLevel, string | null>> = {};
|
|
105
|
+
for (const level of PI_THINKING_LEVELS) {
|
|
106
|
+
if (efforts.has(level)) map[level] = level;
|
|
107
|
+
else if (level === "max" && efforts.has("ultra")) map[level] = "ultra";
|
|
108
|
+
else map[level] = null;
|
|
109
|
+
}
|
|
110
|
+
return map;
|
|
111
|
+
}
|
|
112
|
+
|
|
83
113
|
function normalizeBaseUrl(value: string): string {
|
|
84
114
|
return value.trim().replace(/\/+$/, "");
|
|
85
115
|
}
|
|
@@ -182,15 +212,30 @@ function modelFromPayload(
|
|
|
182
212
|
const id = asNonEmptyString(payload.id);
|
|
183
213
|
if (!id) return undefined;
|
|
184
214
|
|
|
215
|
+
const capabilities = asRecord(payload.capabilities);
|
|
185
216
|
const cost = asRecord(payload.cost);
|
|
217
|
+
const inputModalities = Array.isArray(payload.input)
|
|
218
|
+
? payload.input
|
|
219
|
+
: capabilities?.input_modalities;
|
|
186
220
|
const input: ("text" | "image")[] =
|
|
187
|
-
Array.isArray(
|
|
221
|
+
Array.isArray(inputModalities) && inputModalities.includes("image") ? ["text", "image"] : ["text"];
|
|
222
|
+
const efforts = getReasoningEfforts(payload.reasoning_efforts ?? capabilities?.reasoning_effort);
|
|
223
|
+
const reasoning =
|
|
224
|
+
payload.reasoning === true ||
|
|
225
|
+
payload.supports_reasoning === true ||
|
|
226
|
+
payload.supports_reasoning_effort === true ||
|
|
227
|
+
capabilities?.supports_reasoning === true ||
|
|
228
|
+
efforts.size > 0;
|
|
188
229
|
|
|
189
230
|
return {
|
|
190
231
|
id,
|
|
191
232
|
name: asNonEmptyString(payload.name) ?? id,
|
|
192
233
|
api: API,
|
|
193
|
-
reasoning
|
|
234
|
+
reasoning,
|
|
235
|
+
...(reasoning ? {
|
|
236
|
+
thinkingLevelMap: thinkingLevelMap(efforts),
|
|
237
|
+
compat: { supportsReasoningEffort: true },
|
|
238
|
+
} : {}),
|
|
194
239
|
input,
|
|
195
240
|
cost: {
|
|
196
241
|
input: asPositiveNumber(cost?.input, 0),
|
|
@@ -198,8 +243,14 @@ function modelFromPayload(
|
|
|
198
243
|
cacheRead: asPositiveNumber(cost?.cacheRead, 0),
|
|
199
244
|
cacheWrite: asPositiveNumber(cost?.cacheWrite, 0),
|
|
200
245
|
},
|
|
201
|
-
contextWindow: asPositiveNumber(
|
|
202
|
-
|
|
246
|
+
contextWindow: asPositiveNumber(
|
|
247
|
+
payload.context_window ?? payload.contextWindow ?? capabilities?.context_length,
|
|
248
|
+
DEFAULT_CONTEXT_WINDOW,
|
|
249
|
+
),
|
|
250
|
+
maxTokens: asPositiveNumber(
|
|
251
|
+
payload.max_tokens ?? payload.maxTokens ?? capabilities?.max_output_tokens,
|
|
252
|
+
DEFAULT_MAX_TOKENS,
|
|
253
|
+
),
|
|
203
254
|
};
|
|
204
255
|
}
|
|
205
256
|
|