pi-ollama-cloud 0.2.0 → 0.3.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.
Files changed (3) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/models.ts +66 -19
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.3.0] - 2026-05-04
6
+
7
+ - Derive `thinkingLevelMap` from pi's built-in model definitions instead of hardcoding model-family mappings. The extension now picks up thinking level metadata automatically when pi-mono adds or updates it for any model.
8
+ - Add family-based fallback matching: when an Ollama Cloud model ID doesn't match a pi model ID exactly, the extension now tries matching by model family (via Ollama's `details.family` field). For example, `gemma4:31b` correctly picks up Gemma 4's thinking level map from pi.
9
+
10
+ ## [0.2.1] - 2026-04-29
11
+
12
+ - Fix API key retrieval by using `AuthStorage` instead of `ctx.modelRegistry.getApiKeyForProvider`. The provider-level API key lookup was failing, causing auth to only work when an environment variable was set. Now reads from `auth.json` directly via the pi `AuthStorage` class.
13
+
5
14
  ## [0.2.0] - 2026-04-28
6
15
 
7
16
  - Add `PI_OLLAMA_WEB_TOOLS` environment variable to optionally disable `ollama_web_search` and `ollama_web_fetch` tool registrations. Set to `0`, `false`, `no`, `off`, or an empty string to opt-out. The model provider and `/ollama-cloud-refresh` command remain active regardless.
package/models.ts CHANGED
@@ -1,21 +1,21 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { type ExtensionCommandContext, getAgentDir, type ProviderModelConfig } from "@mariozechner/pi-coding-agent";
4
+ import { AuthStorage } from "@mariozechner/pi-coding-agent";
5
+ import { getModels, getProviders } from "@mariozechner/pi-ai";
4
6
 
5
7
  // --- Constants ---
6
-
7
8
  const CACHE_DIR = join(getAgentDir(), "cache");
8
9
  const CACHE_FILE = join(CACHE_DIR, "ollama-cloud-models.json");
9
10
  const FETCH_TIMEOUT_MS = 10000;
10
11
 
11
- /**
12
- * Base URL for the Ollama Cloud API.
13
- * Defaults to "https://ollama.com"; override with OLLAMA_API_BASE to point at a proxy or self-hosted instance.
14
- */
15
- export const OLLAMA_BASE = (process.env.OLLAMA_API_BASE || "https://ollama.com").replace(/\/+$/, "");
12
+ // --- API fetch ---
13
+ export let OLLAMA_BASE = (process.env.OLLAMA_API_BASE || "https://ollama.com").replace(/\/+$/, "");
16
14
 
17
- // --- Raw API types ---
15
+ // Initialize AuthStorage
16
+ const authStorage = AuthStorage.create();
18
17
 
18
+ // --- Raw API types ---
19
19
  /** Response from POST /api/show */
20
20
  export interface OllamaShowResponse {
21
21
  details: {
@@ -38,7 +38,6 @@ interface CachedData {
38
38
  }
39
39
 
40
40
  // --- Assembly: raw API data -> ProviderModelConfig[] ---
41
-
42
41
  function getContextLength(modelInfo: Record<string, unknown>): number {
43
42
  for (const [key, value] of Object.entries(modelInfo)) {
44
43
  if (key.endsWith(".context_length") && typeof value === "number") {
@@ -48,6 +47,52 @@ function getContextLength(modelInfo: Record<string, unknown>): number {
48
47
  return 128000;
49
48
  }
50
49
 
50
+ // --- Built-in model knowledge index ---
51
+ // Build a lookup of model ID -> thinkingLevelMap from pi's built-in models.
52
+ // This avoids hardcoding model-family mappings: when pi-mono updates its
53
+ // model definitions (e.g. DeepSeek V4's thinking levels), the extension
54
+ // picks up the changes automatically.
55
+ const BUILTIN_THINKING_MAP: Record<string, ProviderModelConfig["thinkingLevelMap"]> = {};
56
+ // Fallback: family stem -> [stem, thinkingLevelMap] pairs for models whose Ollama Cloud
57
+ // ID doesn't match exactly. The stem is derived by stripping provider prefixes and
58
+ // non-alphanumeric characters (e.g. "gemma-4-31b-it" -> "gemma431bit").
59
+ // When looking up an Ollama model by its details.family field, we search for a pi stem
60
+ // that starts with the family stem (e.g. family "gemma4" -> pi "gemma431bit").
61
+ // Entries are sorted longest-first so the most specific match wins.
62
+ const BUILTIN_FAMILY_ENTRIES: [string, NonNullable<ProviderModelConfig["thinkingLevelMap"]>][] = [];
63
+ for (const provider of getProviders()) {
64
+ for (const model of getModels(provider as any)) {
65
+ if (model.thinkingLevelMap) {
66
+ BUILTIN_THINKING_MAP[model.id] = model.thinkingLevelMap;
67
+ const stem = model.id
68
+ .replace(/^[a-z0-9-]+\//, "") // strip provider prefix (e.g. "zai/", "deepseek/")
69
+ .replace(/[^a-zA-Z0-9]/g, "") // strip non-alphanumeric
70
+ .toLowerCase();
71
+ BUILTIN_FAMILY_ENTRIES.push([stem, model.thinkingLevelMap]);
72
+ }
73
+ }
74
+ }
75
+ // Longest stems first so a more specific match (e.g. "gemma431bit") wins over a generic one (e.g. "gemma4").
76
+ BUILTIN_FAMILY_ENTRIES.sort((a, b) => b[0].length - a[0].length);
77
+
78
+ function resolveThinkingLevelMap(modelId: string, data: OllamaShowResponse): ProviderModelConfig["thinkingLevelMap"] {
79
+ // 1. Exact ID match (e.g. "deepseek-v4-pro")
80
+ const exact = BUILTIN_THINKING_MAP[modelId];
81
+ if (exact) return exact;
82
+
83
+ // 2. Family-based fallback: match Ollama's details.family against pi model stems
84
+ if (data.capabilities?.includes("thinking")) {
85
+ const familyStem = data.details.family.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
86
+ for (const [stem, tlm] of BUILTIN_FAMILY_ENTRIES) {
87
+ if (stem.startsWith(familyStem)) {
88
+ return tlm;
89
+ }
90
+ }
91
+ }
92
+
93
+ return undefined;
94
+ }
95
+
51
96
  export function assembleModels(raw: Record<string, OllamaShowResponse>): ProviderModelConfig[] {
52
97
  return Object.entries(raw)
53
98
  .filter(([, data]) => data.capabilities?.includes("tools"))
@@ -55,6 +100,7 @@ export function assembleModels(raw: Record<string, OllamaShowResponse>): Provide
55
100
  id,
56
101
  name: id,
57
102
  reasoning: data.capabilities?.includes("thinking") ?? false,
103
+ thinkingLevelMap: resolveThinkingLevelMap(id, data),
58
104
  input: (data.capabilities?.includes("vision") ? ["text", "image"] : ["text"]) as ("text" | "image")[],
59
105
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
60
106
  contextWindow: getContextLength(data.model_info ?? {}),
@@ -63,7 +109,6 @@ export function assembleModels(raw: Record<string, OllamaShowResponse>): Provide
63
109
  }
64
110
 
65
111
  // --- Fallback models (cold cache) ---
66
-
67
112
  export const FALLBACK_MODELS: ProviderModelConfig[] = [
68
113
  {
69
114
  id: "glm-5.1:cloud",
@@ -86,7 +131,6 @@ export const FALLBACK_MODELS: ProviderModelConfig[] = [
86
131
  ];
87
132
 
88
133
  // --- Cache I/O ---
89
-
90
134
  export function readCache(): Record<string, OllamaShowResponse> | null {
91
135
  try {
92
136
  if (!existsSync(CACHE_FILE)) return null;
@@ -107,17 +151,20 @@ export function writeCache(models: Record<string, OllamaShowResponse>): void {
107
151
  }
108
152
  }
109
153
 
110
- // --- API fetch ---
111
-
112
- /**
113
- * Fetch the full model catalog from Ollama Cloud.
114
- * Returns null on fatal errors (missing API key, list fetch failed, no models fetched);
115
- * the caller can rely on fetchModels having already shown a user-facing error.
116
- */
154
+ // --- Fetch Models ---
117
155
  export async function fetchModels(ctx: ExtensionCommandContext): Promise<Record<string, OllamaShowResponse> | null> {
118
- const apiKey = await ctx.modelRegistry.getApiKeyForProvider("ollama-cloud");
156
+ const apiKey = await authStorage.getApiKey("ollama-cloud");
157
+
119
158
  if (!apiKey) {
120
- ctx.ui.notify("No Ollama Cloud API key configured (auth.json or OLLAMA_API_KEY env var)", "error");
159
+ ctx.ui.notify(
160
+ "No Ollama Cloud API key found. \n" +
161
+ "Please ensure your API key is set in: \n" +
162
+ "- auth.json file (at ~/.pi/agent/auth.json) under 'ollama-cloud' key,\n" +
163
+ "- or via the CLI --api-key flag.\n" +
164
+ "Example auth.json entry: \n" +
165
+ '{ \"ollama-cloud\": { \"type\": \"api_key\", \"key\": \"YOUR_API_KEY\" } }',
166
+ "error",
167
+ );
121
168
  return null;
122
169
  }
123
170
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-ollama-cloud",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "keywords": [
6
6
  "pi-package"