pi-lilac-provider 1.9.3 → 1.9.5

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.
@@ -1 +1,49 @@
1
- {}
1
+ {
2
+ "zai-org/glm-5.1": {
3
+ "id": "zai-org/glm-5.1",
4
+ "name": "GLM 5.1",
5
+ "reasoning": true,
6
+ "input": [
7
+ "text"
8
+ ],
9
+ "cost": {
10
+ "input": 0.9,
11
+ "output": 3,
12
+ "cacheRead": 0.27,
13
+ "cacheWrite": 0
14
+ },
15
+ "contextWindow": 202752,
16
+ "maxTokens": 131072,
17
+ "compat": {
18
+ "supportsDeveloperRole": false,
19
+ "supportsStore": false,
20
+ "maxTokensField": "max_completion_tokens",
21
+ "thinkingFormat": "qwen-chat-template",
22
+ "zaiToolStream": true
23
+ },
24
+ "deprecatedAt": "2026-08-02T19:34:35.890Z"
25
+ },
26
+ "minimaxai/minimax-m2.7": {
27
+ "id": "minimaxai/minimax-m2.7",
28
+ "name": "MiniMax M2.7",
29
+ "reasoning": true,
30
+ "input": [
31
+ "text"
32
+ ],
33
+ "cost": {
34
+ "input": 0.3,
35
+ "output": 1.2,
36
+ "cacheRead": 0.055,
37
+ "cacheWrite": 0
38
+ },
39
+ "contextWindow": 204800,
40
+ "maxTokens": 204800,
41
+ "compat": {
42
+ "supportsDeveloperRole": true,
43
+ "supportsStore": false,
44
+ "maxTokensField": "max_completion_tokens",
45
+ "thinkingFormat": "qwen-chat-template"
46
+ },
47
+ "deprecatedAt": "2026-08-02T19:34:35.890Z"
48
+ }
49
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-lilac-provider",
3
- "version": "1.9.3",
3
+ "version": "1.9.5",
4
4
  "description": "Lilac provider extension for pi - Access Kimi K2.6, GLM 5.1, and Gemma 4 models through Lilac's OpenAI-compatible API on idle GPUs",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -30,7 +30,7 @@
30
30
  "@earendil-works/pi-coding-agent": ">=0.74.0"
31
31
  },
32
32
  "devDependencies": {
33
- "@earendil-works/pi-coding-agent": "0.83.0",
33
+ "@earendil-works/pi-coding-agent": "0.84.0",
34
34
  "@types/node": "25.9.1",
35
35
  "knip": "6.14.1",
36
36
  "typescript": "6.0.3"
package/patch.json CHANGED
@@ -2,15 +2,19 @@
2
2
  "moonshotai/kimi-k2.6": {
3
3
  "reasoning": true,
4
4
  "cost": {
5
- "input": 0.70,
6
- "output": 3.50,
7
- "cacheRead": 0.20
5
+ "input": 0.7,
6
+ "output": 3.5,
7
+ "cacheRead": 0.2
8
8
  },
9
9
  "compat": {
10
10
  "thinkingFormat": "chat-template",
11
11
  "chatTemplateKwargs": {
12
- "thinking": { "$var": "thinking.enabled" },
13
- "enable_thinking": { "$var": "thinking.enabled" },
12
+ "thinking": {
13
+ "$var": "thinking.enabled"
14
+ },
15
+ "enable_thinking": {
16
+ "$var": "thinking.enabled"
17
+ },
14
18
  "preserve_thinking": true
15
19
  },
16
20
  "maxTokensField": "max_completion_tokens",
@@ -18,33 +22,17 @@
18
22
  "supportsStore": false
19
23
  }
20
24
  },
21
- "zai-org/glm-5.1": {
22
- "reasoning": true,
23
- "maxTokens": 131072,
24
- "cost": {
25
- "input": 0.90,
26
- "output": 3.00,
27
- "cacheRead": 0.27
28
- },
29
- "compat": {
30
- "thinkingFormat": "chat-template",
31
- "chatTemplateKwargs": {
32
- "thinking": { "$var": "thinking.enabled" },
33
- "enable_thinking": { "$var": "thinking.enabled" },
34
- "clear_thinking": false
35
- },
36
- "maxTokensField": "max_completion_tokens",
37
- "supportsDeveloperRole": false,
38
- "supportsStore": false,
39
- "zaiToolStream": true
40
- }
41
- },
42
25
  "zai-org/glm-5.2": {
43
26
  "compat": {
44
27
  "thinkingFormat": "chat-template",
45
28
  "chatTemplateKwargs": {
46
- "enable_thinking": { "$var": "thinking.enabled" },
47
- "reasoning_effort": { "$var": "thinking.effort", "omitWhenOff": true },
29
+ "enable_thinking": {
30
+ "$var": "thinking.enabled"
31
+ },
32
+ "reasoning_effort": {
33
+ "$var": "thinking.effort",
34
+ "omitWhenOff": true
35
+ },
48
36
  "clear_thinking": false
49
37
  }
50
38
  },
@@ -66,29 +54,25 @@
66
54
  "compat": {
67
55
  "thinkingFormat": "chat-template",
68
56
  "chatTemplateKwargs": {
69
- "thinking": { "$var": "thinking.enabled" },
70
- "enable_thinking": { "$var": "thinking.enabled" }
57
+ "thinking": {
58
+ "$var": "thinking.enabled"
59
+ },
60
+ "enable_thinking": {
61
+ "$var": "thinking.enabled"
62
+ }
71
63
  },
72
64
  "maxTokensField": "max_completion_tokens",
73
65
  "supportsDeveloperRole": true,
74
66
  "supportsStore": false
75
67
  }
76
68
  },
77
- "minimaxai/minimax-m2.7": {
78
- "compat": {
79
- "thinkingFormat": "chat-template",
80
- "chatTemplateKwargs": {
81
- "thinking": { "$var": "thinking.enabled" },
82
- "enable_thinking": { "$var": "thinking.enabled" }
83
- },
84
- "supportsDeveloperRole": false
85
- }
86
- },
87
69
  "minimaxai/minimax-m3": {
88
70
  "compat": {
89
71
  "thinkingFormat": "chat-template",
90
72
  "chatTemplateKwargs": {
91
- "thinking_mode": { "$var": "thinking.effort" }
73
+ "thinking_mode": {
74
+ "$var": "thinking.effort"
75
+ }
92
76
  }
93
77
  },
94
78
  "thinkingLevelMap": {
@@ -99,5 +83,44 @@
99
83
  "high": "enabled",
100
84
  "xhigh": null
101
85
  }
86
+ },
87
+ "zai-org/glm-5.1": {
88
+ "reasoning": true,
89
+ "maxTokens": 131072,
90
+ "cost": {
91
+ "input": 0.9,
92
+ "output": 3,
93
+ "cacheRead": 0.27
94
+ },
95
+ "compat": {
96
+ "thinkingFormat": "chat-template",
97
+ "chatTemplateKwargs": {
98
+ "thinking": {
99
+ "$var": "thinking.enabled"
100
+ },
101
+ "enable_thinking": {
102
+ "$var": "thinking.enabled"
103
+ },
104
+ "clear_thinking": false
105
+ },
106
+ "maxTokensField": "max_completion_tokens",
107
+ "supportsDeveloperRole": false,
108
+ "supportsStore": false,
109
+ "zaiToolStream": true
110
+ }
111
+ },
112
+ "minimaxai/minimax-m2.7": {
113
+ "compat": {
114
+ "thinkingFormat": "chat-template",
115
+ "chatTemplateKwargs": {
116
+ "thinking": {
117
+ "$var": "thinking.enabled"
118
+ },
119
+ "enable_thinking": {
120
+ "$var": "thinking.enabled"
121
+ }
122
+ },
123
+ "supportsDeveloperRole": false
124
+ }
102
125
  }
103
126
  }
@@ -92,9 +92,20 @@ const PI_AI_API = path.join(
92
92
  );
93
93
  function os_home_pi_ai() {
94
94
  // Resolve the pi-ai package shipped inside the globally-installed pi agent.
95
+ let piAgentSibling = "";
96
+ try {
97
+ const agentPackage = path.join(HERE, "..", "node_modules", "@earendil-works", "pi-coding-agent", "package.json");
98
+ piAgentSibling = path.join(path.dirname(fs.realpathSync(agentPackage)), "..", "pi-ai");
99
+ } catch {
100
+ // The fallback candidates below cover direct pi-ai installs and global pi.
101
+ }
95
102
  const candidates = [
103
+ process.env.PI_AI_PATH,
104
+ piAgentSibling,
105
+ path.join(HERE, "..", "node_modules", "@earendil-works", "pi-ai"),
106
+ path.join(HERE, "..", "node_modules", "@earendil-works", "pi-coding-agent", "node_modules", "@earendil-works", "pi-ai"),
96
107
  "/Users/monotykamary/.npm-global/lib/node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-ai",
97
- ];
108
+ ].filter((candidate): candidate is string => typeof candidate === "string" && candidate.length > 0);
98
109
  for (const c of candidates) if (fs.existsSync(c)) return c;
99
110
  throw new Error("Could not locate pi-ai package in the global pi install.");
100
111
  }
@@ -22,15 +22,119 @@
22
22
  * patch.json and custom-models.json are applied at runtime by the provider.
23
23
  * They are NOT baked into models.json, but ARE used to generate the README table.
24
24
  *
25
- * Requires LILAC_API_KEY environment variable.
25
+ * API key: the stored `lilac` credential in ~/.pi/agent/auth.json wins, then
26
+ * the LILAC_API_KEY environment variable. The script refuses to run without one.
26
27
  */
27
28
 
28
29
  import fs from 'fs';
30
+ import os from 'os';
31
+ import { execSync } from 'child_process';
29
32
  import path from 'path';
30
33
  import { fileURLToPath } from 'url';
31
34
 
32
35
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
33
36
 
37
+ // pi's agent directory: PI_CODING_AGENT_DIR (with ~ expansion) or ~/.pi/agent.
38
+ function piAgentDir() {
39
+ const envDir = process.env.PI_CODING_AGENT_DIR;
40
+ if (envDir) {
41
+ return envDir.startsWith('~/') || envDir === '~'
42
+ ? path.join(os.homedir(), envDir.slice(1))
43
+ : envDir;
44
+ }
45
+ return path.join(os.homedir(), '.pi', 'agent');
46
+ }
47
+
48
+ const AUTH_JSON_PATH = path.join(piAgentDir(), 'auth.json');
49
+
50
+ /**
51
+ * Resolve a configured value using pi's semantics (resolve-config-value.ts in
52
+ * pi-mono): "!command" runs via the shell (10s timeout) and uses trimmed
53
+ * stdout; "$VAR" / "${VAR}" interpolate environment variables ("$$" escapes a
54
+ * literal "$", "$!" a literal "!"); anything else is a literal. Returns
55
+ * undefined when a referenced env var is unset or a command fails.
56
+ */
57
+ function resolveConfigValue(config, env) {
58
+ if (typeof config !== 'string' || config.length === 0) return undefined;
59
+ if (config.startsWith('!')) {
60
+ try {
61
+ const out = execSync(config.slice(1), {
62
+ encoding: 'utf8',
63
+ timeout: 10000,
64
+ stdio: ['ignore', 'pipe', 'ignore'],
65
+ });
66
+ return out.trim() || undefined;
67
+ } catch {
68
+ return undefined;
69
+ }
70
+ }
71
+ const ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
72
+ let resolved = '';
73
+ let index = 0;
74
+ while (index < config.length) {
75
+ const dollar = config.indexOf('$', index);
76
+ if (dollar < 0) {
77
+ resolved += config.slice(index);
78
+ break;
79
+ }
80
+ resolved += config.slice(index, dollar);
81
+ const next = config[dollar + 1];
82
+ let name;
83
+ if (next === '$' || next === '!') {
84
+ resolved += next;
85
+ index = dollar + 2;
86
+ continue;
87
+ } else if (next === '{') {
88
+ const end = config.indexOf('}', dollar + 2);
89
+ if (end < 0) {
90
+ resolved += '$';
91
+ index = dollar + 1;
92
+ continue;
93
+ }
94
+ const inner = config.slice(dollar + 2, end);
95
+ if (!ENV_NAME_RE.test(inner)) {
96
+ resolved += config.slice(dollar, end + 1);
97
+ index = end + 1;
98
+ continue;
99
+ }
100
+ name = inner;
101
+ index = end + 1;
102
+ } else {
103
+ const match = config.slice(dollar + 1).match(/^[A-Za-z_][A-Za-z0-9_]*/);
104
+ if (!match) {
105
+ resolved += '$';
106
+ index = dollar + 1;
107
+ continue;
108
+ }
109
+ name = match[0];
110
+ index = dollar + 1 + name.length;
111
+ }
112
+ const value = (env && env[name]) || process.env[name] || undefined;
113
+ if (value === undefined) return undefined;
114
+ resolved += value;
115
+ }
116
+ return resolved;
117
+ }
118
+
119
+ /**
120
+ * The API key, resolved the way pi itself resolves it for this provider: the
121
+ * stored `lilac` credential in ~/.pi/agent/auth.json wins, then
122
+ * the LILAC_API_KEY environment variable.
123
+ */
124
+ function resolveApiKey() {
125
+ try {
126
+ const auth = JSON.parse(fs.readFileSync(AUTH_JSON_PATH, 'utf8'));
127
+ const credential = auth?.lilac;
128
+ if (credential && credential.type === 'api_key' && typeof credential.key === 'string') {
129
+ const key = resolveConfigValue(credential.key, credential.env);
130
+ if (key) return key;
131
+ }
132
+ } catch {
133
+ // Missing or unparseable auth.json: fall through to the env var.
134
+ }
135
+ return process.env.LILAC_API_KEY || undefined;
136
+ }
137
+
34
138
  const MODELS_API_URL = 'https://api.getlilac.com/v1/models';
35
139
  const MODELS_JSON_PATH = path.join(__dirname, '..', 'models.json');
36
140
  const PATCH_JSON_PATH = path.join(__dirname, '..', 'patch.json');
@@ -63,9 +167,9 @@ function toPerMillion(val) {
63
167
  // ─── API fetch ───────────────────────────────────────────────────────────────
64
168
 
65
169
  async function fetchModels() {
66
- const apiKey = process.env.LILAC_API_KEY;
170
+ const apiKey = resolveApiKey();
67
171
  if (!apiKey) {
68
- throw new Error('LILAC_API_KEY environment variable is required');
172
+ throw new Error('No API key found: no `lilac` credential resolved from ' + AUTH_JSON_PATH + ' and LILAC_API_KEY is not set');
69
173
  }
70
174
 
71
175
  console.log(`Fetching models from ${MODELS_API_URL}...`);