pi-ollama-cloud 0.11.0 → 0.12.0
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/CHANGELOG.md +8 -1
- package/README.md +5 -11
- package/limits.generated.ts +2 -1
- package/models.generated.ts +73 -33
- package/models.ts +2 -2
- package/package.json +6 -4
- package/pricing.generated.ts +3 -2
- package/reasoning.generated.ts +35 -0
- package/thinking-levels.ts +110 -62
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## [
|
|
5
|
+
## [0.12.0] - 2026-09-11
|
|
6
|
+
|
|
7
|
+
- Source per-model thinking levels from models.dev instead of hardcoded maps. `scripts/generate-reasoning.ts` fetches the `ollama-cloud` provider's `reasoning_options` into `reasoning.generated.ts`, and `thinking-levels.ts` maps each model's effort values onto Pi's levels (toggle-only models become a binary on/off map; models with no models.dev entry fall back to `DEFAULT`). The `off` switch is handled by a small override table for models verified not to honor `reasoning_effort:"none"` (`gpt-oss:20b`, `gpt-oss:120b`, `minimax-m2.7`). Removed the now-stale per-family maps and the `docs/think-experiment.md` doc.
|
|
8
|
+
- `generate-models` now also refreshes `reasoning.generated.ts` (runs `generate-pricing`, `generate-reasoning`, then `generate-models`).
|
|
9
|
+
- Fix `generate-pricing` mis-dropping models whose pricing-page cached-input cell is `-` (no cache rate): those rows now match and their `cacheRead` equals `input`. This restored pricing for `mistral-large-3:675b`, `nemotron-3-nano:30b`, and `qwen3.5:397b`, which the earlier regex had left at zero cost.
|
|
10
|
+
- Refresh the model catalog: added `deepseek-v4.1-flash` (probed max output 393216).
|
|
11
|
+
|
|
12
|
+
## [0.11.0] - 2026-09-07
|
|
6
13
|
|
|
7
14
|
- Fix `/ollama-cloud-usage` and the usage status bar failing with "unexpected response shape" after the undocumented `/api/usage` endpoint flipped between a single `limits.monthly` bucket and `limits.session` plus `limits.weekly` (the shape has flip-flopped repeatedly as of 2026-09). Any bucket present (`monthly`, `session`, `weekly`) is accepted alone or in combination, and whichever are present are displayed as `5h`/`7d`/`30d` segments. Thanks @johanngyger (#56).
|
|
8
15
|
- Cache `ollama_web_search` results (24h) and `ollama_web_fetch` pages (24h success / 15 min failure) on disk under the pi agent home, so repeated queries and page reads cost 0 API calls. Expired entries are pruned on write, the cache is capped at 500 entries per kind (oldest evicted beyond the cap; `PI_OLLAMA_SEARCH_MAX_ENTRIES`), a partially corrupted cache file is validated per entry and degrades to "no cache" instead of crashing tool calls, and the file is written with `0600` permissions since it stores page content and URLs that can embed credentials. Tune with `PI_OLLAMA_SEARCH_TTL_HOURS`, `PI_OLLAMA_SEARCH_FAIL_TTL_MINUTES`, `PI_OLLAMA_SEARCH_MAX_ENTRIES`, and `PI_OLLAMA_SEARCH_CACHE_PATH`.
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Registers Ollama Cloud as a model provider with dynamically fetched models, and
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
9
|
- **Dynamic model discovery** - Fetches the full model list from `ollama.com/v1/models`, then fetches per-model details via `/api/show` to determine capabilities, context length, and tool support.
|
|
10
|
-
- **
|
|
10
|
+
- **Data-driven thinking levels** - Maps Pi's thinking levels to Ollama Cloud's OpenAI-compatible `reasoning_effort` values via `thinking-levels.ts`, sourced from models.dev per-model reasoning options with a small override table for the models where `none` doesn't disable thinking.
|
|
11
11
|
- **Baked-in model list** - A generated fallback list (`models.generated.ts`) ships with the extension so models are available on first launch without any network calls. It is only a fallback: pi refreshes the live catalog at runtime, so shipping a new release for catalog freshness is no longer needed.
|
|
12
12
|
- **Automatic model refresh** - On startup, `/model` open, and `pi update --models`, pi calls the extension's `refreshModels` callback to fetch the latest models from the API and persists them through pi's own model store. No manual refresh command.
|
|
13
13
|
- **`ollama_web_search` tool** - Search the web for real-time information using Ollama Cloud's `/api/web_search` endpoint. Returns titles, URLs, and content snippets.
|
|
@@ -132,7 +132,7 @@ Model metadata is derived from the `/api/show` response:
|
|
|
132
132
|
| Field | Source |
|
|
133
133
|
|---|---|
|
|
134
134
|
| `reasoning` | `capabilities` includes `"thinking"` |
|
|
135
|
-
| `thinkingLevelMap` | [`thinking-levels.ts`](thinking-levels.ts)
|
|
135
|
+
| `thinkingLevelMap` | [`thinking-levels.ts`](thinking-levels.ts) + [`reasoning.generated.ts`](reasoning.generated.ts) (models.dev reasoning options), with an `off` override table for models that ignore `none` |
|
|
136
136
|
| `input` | `["text", "image"]` if `capabilities` includes `"vision"`, else `["text"]` |
|
|
137
137
|
| `contextWindow` | `model_info.*.context_length` (falls back to 128000) |
|
|
138
138
|
| `maxTokens` | Probed per-model limits from [`limits.generated.ts`](limits.generated.ts), generated by `scripts/generate-limits.ts` (requires `OLLAMA_API_KEY`). Models without a probed limit fall back to 32768. |
|
|
@@ -146,17 +146,11 @@ Cache pricing is informational only: the `/pricing` page lists a "Cached input"
|
|
|
146
146
|
|
|
147
147
|
### Thinking level mapping
|
|
148
148
|
|
|
149
|
-
Pi's thinking levels are mapped to Ollama Cloud's OpenAI-compatible `reasoning_effort` parameter in [`thinking-levels.ts`](thinking-levels.ts). The API accepts `none`, `low`, `medium`, `high`, and `max`. Effects of `max` over `high` vary by model and prompt difficulty
|
|
149
|
+
Pi's thinking levels are mapped to Ollama Cloud's OpenAI-compatible `reasoning_effort` parameter in [`thinking-levels.ts`](thinking-levels.ts). The API accepts `none`, `low`, `medium`, `high`, `xhigh`, and `max`. Effects of `max` over `high` vary by model and prompt difficulty.
|
|
150
150
|
|
|
151
|
-
|
|
152
|
-
|---|---|---|---|
|
|
153
|
-
| `DEFAULT` | Most thinking models | off, low, medium, high, xhigh | `minimal` hidden (duplicate of low) |
|
|
154
|
-
| `GPT_OSS` | `gpt-oss*` | low, medium, high | Can't disable thinking, no off or xhigh |
|
|
155
|
-
| `QWEN3` | `qwen3*` (except `qwen3-vl*`) | off, medium | Binary-only (think/nothink), no gradation |
|
|
156
|
-
| `GLM_52` | `glm-5.2` | off, high, xhigh | GLM supports disabled thinking; Ollama's model page confirms `high` and `max` reasoning efforts |
|
|
157
|
-
| `NO_OFF` | `qwen3-vl*`, `kimi-k2-thinking`, `minimax*` | low, medium, high, xhigh | "none" doesn't disable thinking on these models |
|
|
151
|
+
Per-model support is sourced from models.dev: [`scripts/generate-reasoning.ts`](scripts/generate-reasoning.ts) fetches the `ollama-cloud` provider's `reasoning_options` into `reasoning.generated.ts`, and `resolve()` maps each model's effort values onto Pi's levels. Models with `effort` values expose those grades; `toggle`-only models expose a single on/off level. Models with no models.dev entry fall back to `DEFAULT`.
|
|
158
152
|
|
|
159
|
-
|
|
153
|
+
Because the API reports only a boolean `thinking` capability and models.dev does not reliably encode the `none` behavior, the `off` switch is handled via a small override table in `thinking-levels.ts`: it defaults to enabled, and is hidden only for models verified (by live probing) not to honor `reasoning_effort:"none"` - currently `gpt-oss:20b`, `gpt-oss:120b`, and `minimax-m2.7`. The per-model metadata gaps behind the models.dev sourcing are tracked upstream in [ollama/ollama#18385](https://github.com/ollama/ollama/issues/18385).
|
|
160
154
|
|
|
161
155
|
## Tools
|
|
162
156
|
|
package/limits.generated.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// Auto-generated by scripts/generate-limits.ts
|
|
2
2
|
// Do not edit manually.
|
|
3
|
-
//
|
|
3
|
+
// Entries: 20
|
|
4
4
|
|
|
5
5
|
export const MODEL_MAX_OUTPUT_TOKENS: Record<string, number> = {
|
|
6
6
|
"deepseek-v4-flash:0731": 65536,
|
|
7
7
|
"deepseek-v4-pro:0813": 65536,
|
|
8
|
+
"deepseek-v4.1-flash": 393216,
|
|
8
9
|
"gemma4:31b": 262144,
|
|
9
10
|
"glm-5.1": 131072,
|
|
10
11
|
"glm-5.2": 131072,
|
package/models.generated.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Auto-generated by scripts/generate-models.ts
|
|
2
2
|
// Do not edit manually.
|
|
3
|
-
// Generated: 2026-09-
|
|
4
|
-
// Model count:
|
|
3
|
+
// Generated: 2026-09-12T02:41:57.025Z
|
|
4
|
+
// Model count: 20
|
|
5
5
|
|
|
6
6
|
import type { ProviderModelConfig } from "@earendil-works/pi-coding-agent";
|
|
7
7
|
|
|
@@ -39,8 +39,8 @@ export const GENERATED_MODELS: ProviderModelConfig[] = [
|
|
|
39
39
|
reasoning: true,
|
|
40
40
|
thinkingLevelMap: {
|
|
41
41
|
high: "high",
|
|
42
|
-
low:
|
|
43
|
-
medium:
|
|
42
|
+
low: null,
|
|
43
|
+
medium: null,
|
|
44
44
|
minimal: null,
|
|
45
45
|
off: "none",
|
|
46
46
|
xhigh: "max",
|
|
@@ -77,10 +77,50 @@ export const GENERATED_MODELS: ProviderModelConfig[] = [
|
|
|
77
77
|
input: ["text"],
|
|
78
78
|
maxTokens: 65536,
|
|
79
79
|
reasoning: true,
|
|
80
|
+
thinkingLevelMap: {
|
|
81
|
+
high: "high",
|
|
82
|
+
low: null,
|
|
83
|
+
medium: null,
|
|
84
|
+
minimal: null,
|
|
85
|
+
off: "none",
|
|
86
|
+
xhigh: "max",
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: "deepseek-v4.1-flash",
|
|
91
|
+
name: "deepseek-v4.1-flash",
|
|
92
|
+
compat: {
|
|
93
|
+
maxTokensField: "max_tokens",
|
|
94
|
+
openRouterRouting: {},
|
|
95
|
+
requiresAssistantAfterToolResult: false,
|
|
96
|
+
requiresReasoningContentOnAssistantMessages: false,
|
|
97
|
+
requiresThinkingAsText: false,
|
|
98
|
+
requiresToolResultName: false,
|
|
99
|
+
sendSessionAffinityHeaders: false,
|
|
100
|
+
supportsDeveloperRole: false,
|
|
101
|
+
supportsLongCacheRetention: false,
|
|
102
|
+
supportsReasoningEffort: true,
|
|
103
|
+
supportsStore: false,
|
|
104
|
+
supportsStrictMode: false,
|
|
105
|
+
supportsUsageInStreaming: true,
|
|
106
|
+
thinkingFormat: "openai",
|
|
107
|
+
vercelGatewayRouting: {},
|
|
108
|
+
zaiToolStream: false,
|
|
109
|
+
},
|
|
110
|
+
contextWindow: 1048576,
|
|
111
|
+
cost: {
|
|
112
|
+
cacheRead: 0.006,
|
|
113
|
+
cacheWrite: 0,
|
|
114
|
+
input: 0.3,
|
|
115
|
+
output: 1.2,
|
|
116
|
+
},
|
|
117
|
+
input: ["text", "image"],
|
|
118
|
+
maxTokens: 393216,
|
|
119
|
+
reasoning: true,
|
|
80
120
|
thinkingLevelMap: {
|
|
81
121
|
high: "high",
|
|
82
122
|
low: "low",
|
|
83
|
-
medium:
|
|
123
|
+
medium: null,
|
|
84
124
|
minimal: null,
|
|
85
125
|
off: "none",
|
|
86
126
|
xhigh: "max",
|
|
@@ -118,12 +158,12 @@ export const GENERATED_MODELS: ProviderModelConfig[] = [
|
|
|
118
158
|
maxTokens: 262144,
|
|
119
159
|
reasoning: true,
|
|
120
160
|
thinkingLevelMap: {
|
|
121
|
-
high:
|
|
122
|
-
low:
|
|
161
|
+
high: null,
|
|
162
|
+
low: null,
|
|
123
163
|
medium: "medium",
|
|
124
164
|
minimal: null,
|
|
125
165
|
off: "none",
|
|
126
|
-
xhigh:
|
|
166
|
+
xhigh: null,
|
|
127
167
|
},
|
|
128
168
|
},
|
|
129
169
|
{
|
|
@@ -158,12 +198,12 @@ export const GENERATED_MODELS: ProviderModelConfig[] = [
|
|
|
158
198
|
maxTokens: 131072,
|
|
159
199
|
reasoning: true,
|
|
160
200
|
thinkingLevelMap: {
|
|
161
|
-
high:
|
|
162
|
-
low:
|
|
201
|
+
high: null,
|
|
202
|
+
low: null,
|
|
163
203
|
medium: "medium",
|
|
164
204
|
minimal: null,
|
|
165
205
|
off: "none",
|
|
166
|
-
xhigh:
|
|
206
|
+
xhigh: null,
|
|
167
207
|
},
|
|
168
208
|
},
|
|
169
209
|
{
|
|
@@ -240,7 +280,7 @@ export const GENERATED_MODELS: ProviderModelConfig[] = [
|
|
|
240
280
|
thinkingLevelMap: {
|
|
241
281
|
high: "high",
|
|
242
282
|
low: "low",
|
|
243
|
-
medium:
|
|
283
|
+
medium: null,
|
|
244
284
|
minimal: null,
|
|
245
285
|
off: "none",
|
|
246
286
|
xhigh: "max",
|
|
@@ -280,7 +320,7 @@ export const GENERATED_MODELS: ProviderModelConfig[] = [
|
|
|
280
320
|
thinkingLevelMap: {
|
|
281
321
|
high: "high",
|
|
282
322
|
low: "low",
|
|
283
|
-
medium:
|
|
323
|
+
medium: null,
|
|
284
324
|
minimal: null,
|
|
285
325
|
off: "none",
|
|
286
326
|
xhigh: "max",
|
|
@@ -398,12 +438,12 @@ export const GENERATED_MODELS: ProviderModelConfig[] = [
|
|
|
398
438
|
maxTokens: 262144,
|
|
399
439
|
reasoning: true,
|
|
400
440
|
thinkingLevelMap: {
|
|
401
|
-
high:
|
|
402
|
-
low:
|
|
441
|
+
high: null,
|
|
442
|
+
low: null,
|
|
403
443
|
medium: "medium",
|
|
404
444
|
minimal: null,
|
|
405
445
|
off: "none",
|
|
406
|
-
xhigh:
|
|
446
|
+
xhigh: null,
|
|
407
447
|
},
|
|
408
448
|
},
|
|
409
449
|
{
|
|
@@ -438,12 +478,12 @@ export const GENERATED_MODELS: ProviderModelConfig[] = [
|
|
|
438
478
|
maxTokens: 262144,
|
|
439
479
|
reasoning: true,
|
|
440
480
|
thinkingLevelMap: {
|
|
441
|
-
high:
|
|
442
|
-
low:
|
|
481
|
+
high: null,
|
|
482
|
+
low: null,
|
|
443
483
|
medium: "medium",
|
|
444
484
|
minimal: null,
|
|
445
485
|
off: "none",
|
|
446
|
-
xhigh:
|
|
486
|
+
xhigh: null,
|
|
447
487
|
},
|
|
448
488
|
},
|
|
449
489
|
{
|
|
@@ -480,7 +520,7 @@ export const GENERATED_MODELS: ProviderModelConfig[] = [
|
|
|
480
520
|
thinkingLevelMap: {
|
|
481
521
|
high: "high",
|
|
482
522
|
low: "low",
|
|
483
|
-
medium:
|
|
523
|
+
medium: null,
|
|
484
524
|
minimal: null,
|
|
485
525
|
off: "none",
|
|
486
526
|
xhigh: "max",
|
|
@@ -518,12 +558,12 @@ export const GENERATED_MODELS: ProviderModelConfig[] = [
|
|
|
518
558
|
maxTokens: 131072,
|
|
519
559
|
reasoning: true,
|
|
520
560
|
thinkingLevelMap: {
|
|
521
|
-
high:
|
|
522
|
-
low:
|
|
561
|
+
high: null,
|
|
562
|
+
low: null,
|
|
523
563
|
medium: "medium",
|
|
524
564
|
minimal: null,
|
|
525
565
|
off: null,
|
|
526
|
-
xhigh:
|
|
566
|
+
xhigh: null,
|
|
527
567
|
},
|
|
528
568
|
},
|
|
529
569
|
{
|
|
@@ -562,7 +602,7 @@ export const GENERATED_MODELS: ProviderModelConfig[] = [
|
|
|
562
602
|
low: "low",
|
|
563
603
|
medium: "medium",
|
|
564
604
|
minimal: null,
|
|
565
|
-
off:
|
|
605
|
+
off: "none",
|
|
566
606
|
xhigh: "max",
|
|
567
607
|
},
|
|
568
608
|
},
|
|
@@ -630,12 +670,12 @@ export const GENERATED_MODELS: ProviderModelConfig[] = [
|
|
|
630
670
|
maxTokens: 131072,
|
|
631
671
|
reasoning: true,
|
|
632
672
|
thinkingLevelMap: {
|
|
633
|
-
high:
|
|
634
|
-
low:
|
|
673
|
+
high: null,
|
|
674
|
+
low: null,
|
|
635
675
|
medium: "medium",
|
|
636
676
|
minimal: null,
|
|
637
677
|
off: "none",
|
|
638
|
-
xhigh:
|
|
678
|
+
xhigh: null,
|
|
639
679
|
},
|
|
640
680
|
},
|
|
641
681
|
{
|
|
@@ -670,12 +710,12 @@ export const GENERATED_MODELS: ProviderModelConfig[] = [
|
|
|
670
710
|
maxTokens: 65536,
|
|
671
711
|
reasoning: true,
|
|
672
712
|
thinkingLevelMap: {
|
|
673
|
-
high:
|
|
674
|
-
low:
|
|
713
|
+
high: null,
|
|
714
|
+
low: null,
|
|
675
715
|
medium: "medium",
|
|
676
716
|
minimal: null,
|
|
677
717
|
off: "none",
|
|
678
|
-
xhigh:
|
|
718
|
+
xhigh: null,
|
|
679
719
|
},
|
|
680
720
|
},
|
|
681
721
|
{
|
|
@@ -710,12 +750,12 @@ export const GENERATED_MODELS: ProviderModelConfig[] = [
|
|
|
710
750
|
maxTokens: 65536,
|
|
711
751
|
reasoning: true,
|
|
712
752
|
thinkingLevelMap: {
|
|
713
|
-
high:
|
|
714
|
-
low:
|
|
753
|
+
high: null,
|
|
754
|
+
low: null,
|
|
715
755
|
medium: "medium",
|
|
716
756
|
minimal: null,
|
|
717
757
|
off: "none",
|
|
718
|
-
xhigh:
|
|
758
|
+
xhigh: null,
|
|
719
759
|
},
|
|
720
760
|
},
|
|
721
761
|
{
|
package/models.ts
CHANGED
|
@@ -93,7 +93,7 @@ function buildCompat(): ProviderModelConfig["compat"] {
|
|
|
93
93
|
return {
|
|
94
94
|
// Ollama uses "system" role, not "developer" (ollama: docs.ollama.com/api/openai-compatibility, pi: types.ts#supportsDeveloperRole).
|
|
95
95
|
supportsDeveloperRole: false,
|
|
96
|
-
// reasoning_effort works (ollama: docs.ollama.com/api/openai-compatibility, pi: types.ts#supportsReasoningEffort
|
|
96
|
+
// reasoning_effort works (ollama: docs.ollama.com/api/openai-compatibility, pi: types.ts#supportsReasoningEffort).
|
|
97
97
|
supportsReasoningEffort: true,
|
|
98
98
|
// "store" is not a supported field (ollama: docs.ollama.com/api/openai-compatibility, pi: types.ts#supportsStore).
|
|
99
99
|
supportsStore: false,
|
|
@@ -109,7 +109,7 @@ function buildCompat(): ProviderModelConfig["compat"] {
|
|
|
109
109
|
requiresThinkingAsText: false,
|
|
110
110
|
// DeepSeek-specific, not needed for Ollama (pi: types.ts#requiresReasoningContentOnAssistantMessages).
|
|
111
111
|
requiresReasoningContentOnAssistantMessages: false,
|
|
112
|
-
// reasoning_effort format works (pi: types.ts#thinkingFormat
|
|
112
|
+
// reasoning_effort format works (pi: types.ts#thinkingFormat).
|
|
113
113
|
thinkingFormat: "openai",
|
|
114
114
|
// Ollama does not support tool_choice, so strict mode is unavailable (ollama: docs.ollama.com/api/openai-compatibility, pi: types.ts#supportsStrictMode).
|
|
115
115
|
supportsStrictMode: false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-ollama-cloud",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package"
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"models.ts",
|
|
14
14
|
"models.generated.ts",
|
|
15
15
|
"pricing.generated.ts",
|
|
16
|
+
"reasoning.generated.ts",
|
|
16
17
|
"thinking-levels.ts",
|
|
17
18
|
"usage.ts",
|
|
18
19
|
"utils.ts",
|
|
@@ -32,8 +33,9 @@
|
|
|
32
33
|
"format": "biome format --write .",
|
|
33
34
|
"test": "vitest run",
|
|
34
35
|
"smoke:web-tools": "tsx scripts/smoke-web-tools.ts",
|
|
35
|
-
"generate-models": "tsx scripts/generate-pricing.ts && tsx scripts/generate-models.ts && biome format --write models.generated.ts pricing.generated.ts",
|
|
36
|
-
"generate-limits": "tsx scripts/generate-limits.ts
|
|
36
|
+
"generate-models": "tsx scripts/generate-pricing.ts && tsx scripts/generate-reasoning.ts && tsx scripts/generate-models.ts && biome format --write models.generated.ts pricing.generated.ts reasoning.generated.ts",
|
|
37
|
+
"generate-limits": "tsx scripts/generate-limits.ts",
|
|
38
|
+
"generate-reasoning": "tsx scripts/generate-reasoning.ts && biome format --write reasoning.generated.ts"
|
|
37
39
|
},
|
|
38
40
|
"pi": {
|
|
39
41
|
"extensions": [
|
|
@@ -51,6 +53,6 @@
|
|
|
51
53
|
"@types/node": "^26.1.2",
|
|
52
54
|
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
53
55
|
"tsx": "^4.19.0",
|
|
54
|
-
"vitest": "^4.1.
|
|
56
|
+
"vitest": "^4.1.11"
|
|
55
57
|
}
|
|
56
58
|
}
|
package/pricing.generated.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Auto-generated by scripts/generate-pricing.ts
|
|
2
2
|
// Do not edit manually.
|
|
3
|
-
// Generated: 2026-09-
|
|
4
|
-
// Model count:
|
|
3
|
+
// Generated: 2026-09-12T02:41:55.206Z
|
|
4
|
+
// Model count: 20
|
|
5
5
|
|
|
6
6
|
export interface ModelPrice {
|
|
7
7
|
input: number;
|
|
@@ -13,6 +13,7 @@ export interface ModelPrice {
|
|
|
13
13
|
export const MODEL_PRICING: Record<string, ModelPrice> = {
|
|
14
14
|
"deepseek-v4-flash:0731": { input: 0.44, output: 1.32, cacheRead: 0.014, cacheWrite: 0 },
|
|
15
15
|
"deepseek-v4-pro:0813": { input: 1.32, output: 3.96, cacheRead: 0.044, cacheWrite: 0 },
|
|
16
|
+
"deepseek-v4.1-flash": { input: 0.3, output: 1.2, cacheRead: 0.006, cacheWrite: 0 },
|
|
16
17
|
"gemma4:31b": { input: 0.14, output: 0.4, cacheRead: 0.05, cacheWrite: 0 },
|
|
17
18
|
"glm-5.1": { input: 1, output: 3.2, cacheRead: 0.2, cacheWrite: 0 },
|
|
18
19
|
"glm-5.2": { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 },
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Auto-generated by scripts/generate-reasoning.ts
|
|
2
|
+
// Do not edit manually.
|
|
3
|
+
// Model count: 22
|
|
4
|
+
|
|
5
|
+
export type ModelsDevReasoningOption =
|
|
6
|
+
| { type: "toggle" }
|
|
7
|
+
| {
|
|
8
|
+
type: "effort";
|
|
9
|
+
values: Array<"none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | "ultra" | "default" | null>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const MODEL_REASONING_OPTIONS: Record<string, ModelsDevReasoningOption[]> = {
|
|
13
|
+
"deepseek-v4-flash": [{ type: "toggle" }, { type: "effort", values: ["high", "max"] }],
|
|
14
|
+
"deepseek-v4-flash:0731": [{ type: "toggle" }, { type: "effort", values: ["high", "max"] }],
|
|
15
|
+
"deepseek-v4-pro": [{ type: "toggle" }, { type: "effort", values: ["high", "max"] }],
|
|
16
|
+
"deepseek-v4.1-flash": [{ type: "toggle" }, { type: "effort", values: ["low", "high", "max"] }],
|
|
17
|
+
"gemma4:31b": [{ type: "toggle" }],
|
|
18
|
+
"glm-5.1": [{ type: "toggle" }],
|
|
19
|
+
"glm-5.2": [{ type: "effort", values: ["high", "max"] }],
|
|
20
|
+
"glm-5.3": [{ type: "effort", values: ["low", "high", "max"] }],
|
|
21
|
+
"glm-5.3-flash": [{ type: "effort", values: ["low", "high", "max"] }],
|
|
22
|
+
"gpt-oss:120b": [{ type: "effort", values: ["low", "medium", "high"] }],
|
|
23
|
+
"gpt-oss:20b": [{ type: "effort", values: ["low", "medium", "high"] }],
|
|
24
|
+
"kimi-k2.5": [{ type: "toggle" }],
|
|
25
|
+
"kimi-k2.6": [{ type: "toggle" }],
|
|
26
|
+
"kimi-k2.7-code": [{ type: "toggle" }],
|
|
27
|
+
"kimi-k3": [{ type: "toggle" }, { type: "effort", values: ["low", "high", "max"] }],
|
|
28
|
+
"minimax-m2.5": [],
|
|
29
|
+
"minimax-m2.7": [{ type: "toggle" }],
|
|
30
|
+
"minimax-m3": [{ type: "toggle" }, { type: "effort", values: ["low", "medium", "high", "max"] }],
|
|
31
|
+
"nemotron-3-nano:30b": [{ type: "toggle" }],
|
|
32
|
+
"nemotron-3-super": [{ type: "toggle" }],
|
|
33
|
+
"nemotron-3-ultra": [{ type: "toggle" }],
|
|
34
|
+
"qwen3.5:397b": [{ type: "toggle" }],
|
|
35
|
+
};
|
package/thinking-levels.ts
CHANGED
|
@@ -2,26 +2,36 @@
|
|
|
2
2
|
* Thinking level mapping for Ollama Cloud models.
|
|
3
3
|
*
|
|
4
4
|
* Maps Pi's thinking levels to Ollama Cloud's OpenAI-compatible
|
|
5
|
-
* `reasoning_effort` values. The API accepts "
|
|
6
|
-
* "high", and "max". On simple prompts, "max" can
|
|
7
|
-
* "high", but on harder prompts it can increase thinking
|
|
8
|
-
*
|
|
5
|
+
* `reasoning_effort` values. The API accepts "minimal", "none", "low",
|
|
6
|
+
* "medium", "high", "xhigh", "ultra", and "max". On simple prompts, "max" can
|
|
7
|
+
* be a no-op over "high", but on harder prompts it can increase thinking
|
|
8
|
+
* substantially.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* Every value EFFORT_TO_LEVEL can send was verified against the live chat
|
|
11
|
+
* completions API (2026-09-11: "minimal", "xhigh", and "ultra" probed across
|
|
12
|
+
* gpt-oss, deepseek-v4, glm, minimax, and qwen thinking models, all accepted
|
|
13
|
+
* with graded reasoning), so a future models.dev row that lists them passes
|
|
14
|
+
* through a value the endpoint demonstrably accepts.
|
|
15
|
+
*
|
|
16
|
+
* The per-model level support comes from models.dev: scripts/generate-reasoning.ts
|
|
17
|
+
* fetches the `ollama-cloud` provider's `reasoning_options` into
|
|
18
|
+
* reasoning.generated.ts (the same data source pi uses for its built-in
|
|
19
|
+
* providers), and resolve() maps a model's effort values onto Pi's levels.
|
|
20
|
+
* We fall back to models.dev because the Cloud API does not yet expose
|
|
21
|
+
* per-model supported levels (tracked upstream: https://github.com/ollama/ollama/issues/18385).
|
|
11
22
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* - Kimi K2 Thinking: "none" doesn't disable thinking - off is hidden
|
|
19
|
-
* - MiniMax M2.x: "none" doesn't disable thinking - off is hidden
|
|
23
|
+
* The API exposes only a boolean `thinking` capability plus a global effort
|
|
24
|
+
* vocabulary, and models.dev does not reliably encode the `none` behavior, so
|
|
25
|
+
* the `off` switch is handled separately: it defaults to "none" (a live probe
|
|
26
|
+
* of the current catalog confirmed every model except the OFF_NULL overrides
|
|
27
|
+
* below honors it), and models verified not to honor `none` pin it to null
|
|
28
|
+
* (hidden) via OFF_NULL.
|
|
20
29
|
*
|
|
21
|
-
*
|
|
30
|
+
* A `null` value means the level is hidden in Pi's UI.
|
|
22
31
|
*/
|
|
23
32
|
|
|
24
33
|
import type { ProviderModelConfig } from "@earendil-works/pi-coding-agent";
|
|
34
|
+
import { MODEL_REASONING_OPTIONS, type ModelsDevReasoningOption } from "./reasoning.generated.ts";
|
|
25
35
|
|
|
26
36
|
export type ThinkingLevelMap = NonNullable<ProviderModelConfig["thinkingLevelMap"]>;
|
|
27
37
|
|
|
@@ -35,63 +45,101 @@ export const DEFAULT: ThinkingLevelMap = {
|
|
|
35
45
|
xhigh: "max",
|
|
36
46
|
};
|
|
37
47
|
|
|
38
|
-
/**
|
|
39
|
-
*
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Models where a live probe of `reasoning_effort:"none"` still produced
|
|
50
|
+
* reasoning (i.e. thinking cannot be disabled), so the `off` level is hidden.
|
|
51
|
+
* Confirmed against the current catalog by scripts/../test probing; the API
|
|
52
|
+
* and models.dev do not expose this behavior.
|
|
53
|
+
*/
|
|
54
|
+
/**
|
|
55
|
+
* Models where a live probe of `reasoning_effort:"none"` still produced
|
|
56
|
+
* reasoning (i.e. thinking cannot be disabled), so the `off` level is hidden.
|
|
57
|
+
* Confirmed against the current catalog by scripts/../test probing; the API
|
|
58
|
+
* and models.dev do not expose this behavior.
|
|
59
|
+
*
|
|
60
|
+
* Exact ids hide only the named variant; family prefixes cover future model
|
|
61
|
+
* revisions in the same family. gpt-oss is a family-wide prefix because both
|
|
62
|
+
* probed variants leak and the behavior is documented for the family.
|
|
63
|
+
* minimax is NOT matched family-wide here: minimax-m3 was probed to honor
|
|
64
|
+
* `none`, so only the verified-leaking minimax-m2.7 is pinned by exact id.
|
|
65
|
+
*/
|
|
66
|
+
const OFF_NULL_EXACT = new Set(["minimax-m2.7"]);
|
|
67
|
+
const OFF_NULL_FAMILIES = ["gpt-oss"];
|
|
48
68
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
off: "none",
|
|
53
|
-
minimal: null,
|
|
54
|
-
low: null,
|
|
55
|
-
medium: "medium",
|
|
56
|
-
high: null,
|
|
57
|
-
xhigh: null,
|
|
58
|
-
};
|
|
69
|
+
function hidesOff(id: string): boolean {
|
|
70
|
+
return OFF_NULL_EXACT.has(id) || OFF_NULL_FAMILIES.some((prefix) => id.startsWith(prefix));
|
|
71
|
+
}
|
|
59
72
|
|
|
60
|
-
/**
|
|
61
|
-
*
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Map a models.dev `effort` value onto the Pi level key and the reasoning_effort
|
|
75
|
+
* string to send for it. Ollama's top effort value is "max"; Pi exposes it via
|
|
76
|
+
* the extra-high level, so "max" (and "xhigh"/"ultra") map to the xhigh key.
|
|
77
|
+
*/
|
|
78
|
+
const EFFORT_TO_LEVEL: Record<string, { key: "minimal" | "low" | "medium" | "high" | "xhigh"; value: string }> = {
|
|
79
|
+
minimal: { key: "minimal", value: "minimal" },
|
|
80
|
+
low: { key: "low", value: "low" },
|
|
81
|
+
medium: { key: "medium", value: "medium" },
|
|
82
|
+
high: { key: "high", value: "high" },
|
|
83
|
+
xhigh: { key: "xhigh", value: "xhigh" },
|
|
84
|
+
max: { key: "xhigh", value: "max" },
|
|
85
|
+
ultra: { key: "xhigh", value: "ultra" },
|
|
69
86
|
};
|
|
87
|
+
/**
|
|
88
|
+
* Build a ThinkingLevelMap from models.dev reasoning_options.
|
|
89
|
+
* Levels come from `effort` values (mapped via EFFORT_TO_LEVEL); `off` defaults
|
|
90
|
+
* to "none" (probe-derived, see file header) and is hidden only via the
|
|
91
|
+
* OFF_NULL exact/family sets (see hidesOff).
|
|
92
|
+
* A toggle-only model is binary (on/off) and exposes a single "medium" level.
|
|
93
|
+
*/
|
|
94
|
+
function buildMap(options: readonly ModelsDevReasoningOption[], id: string): ThinkingLevelMap {
|
|
95
|
+
const map: ThinkingLevelMap = {
|
|
96
|
+
off: hidesOff(id) ? null : "none",
|
|
97
|
+
minimal: null,
|
|
98
|
+
low: null,
|
|
99
|
+
medium: null,
|
|
100
|
+
high: null,
|
|
101
|
+
xhigh: null,
|
|
102
|
+
};
|
|
70
103
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
104
|
+
if (options.length > 0 && options.every((option) => option.type === "toggle")) {
|
|
105
|
+
// Binary on/off model: no graded effort, expose a single level.
|
|
106
|
+
return { ...map, medium: "medium" };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const efforts = options.flatMap((option) => (option.type === "effort" ? (option.values ?? []) : []));
|
|
110
|
+
for (const effort of efforts) {
|
|
111
|
+
const target = effort !== null && effort !== "default" ? EFFORT_TO_LEVEL[effort] : undefined;
|
|
112
|
+
if (target) map[target.key] = target.value;
|
|
113
|
+
}
|
|
114
|
+
return map;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Read MODEL_REASONING_OPTIONS[id] without tripping over inherited keys (e.g.
|
|
119
|
+
* "constructor"), which would otherwise resolve to the Object constructor and
|
|
120
|
+
* crash buildMap.
|
|
121
|
+
*/
|
|
122
|
+
function ownOptions(id: string): ModelsDevReasoningOption[] | undefined {
|
|
123
|
+
return Object.hasOwn(MODEL_REASONING_OPTIONS, id) ? MODEL_REASONING_OPTIONS[id] : undefined;
|
|
124
|
+
}
|
|
81
125
|
|
|
82
126
|
/**
|
|
83
127
|
* Resolve the thinking level map for a model.
|
|
84
|
-
*
|
|
128
|
+
* Looks up the model id (exact, then `:tag` family) in the generated models.dev
|
|
129
|
+
* table, falling back to DEFAULT for models with no entry. The matched key is
|
|
130
|
+
* the one passed to buildMap so the OFF_NULL set (keyed on bare family names)
|
|
131
|
+
* applies to tagged ids that resolve through a family match.
|
|
85
132
|
*/
|
|
86
133
|
export function resolve(id: string, capabilities: string[]): ThinkingLevelMap | undefined {
|
|
87
134
|
if (!capabilities.includes("thinking")) return undefined;
|
|
88
135
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return DEFAULT;
|
|
136
|
+
const colon = id.lastIndexOf(":");
|
|
137
|
+
const exact = ownOptions(id);
|
|
138
|
+
const matchedKey = exact !== undefined ? id : colon > 0 ? id.slice(0, colon) : "";
|
|
139
|
+
const options = exact ?? ownOptions(matchedKey);
|
|
140
|
+
// An empty array (e.g. minimax-m2.5) carries no verified options; fall back
|
|
141
|
+
// to DEFAULT rather than a degenerate map whose only selectable level can
|
|
142
|
+
// be a leaking off.
|
|
143
|
+
if (options === undefined || options.length === 0) return DEFAULT;
|
|
144
|
+
return buildMap(options, matchedKey);
|
|
97
145
|
}
|