decorated-pi 0.1.0 → 0.2.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.
@@ -10,6 +10,7 @@ import { setupSubdirAgents } from "./subdir-agents";
10
10
  import { setupSessionTitle } from "./session-title";
11
11
  import { setupGuidance } from "./guidance";
12
12
  import { setupLsp } from "./lsp/index";
13
+ import { setupProviders } from "./providers/index";
13
14
  import { setupSmartAt } from "./smart-at";
14
15
 
15
16
  export default function (pi: ExtensionAPI) {
@@ -20,5 +21,6 @@ export default function (pi: ExtensionAPI) {
20
21
  setupSessionTitle(pi);
21
22
  setupGuidance(pi);
22
23
  setupLsp(pi);
24
+ setupProviders(pi);
23
25
  setupSmartAt(pi);
24
26
  }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * ARK Coding Plan — OAuth/subscription provider with hardcoded models
3
+ *
4
+ * Provider: "ark-coding"
5
+ * Base URL: https://ark.cn-beijing.volces.com/api/coding/v3 (OpenAI-compatible)
6
+ * Auth: OAuth/subscription login → prompt for API key
7
+ *
8
+ * All models hardcoded. No dynamic fetching, no config file caching.
9
+ * - No auth → no models in /model (clean UX, via hasConfiguredAuth)
10
+ * - Login → models become available immediately
11
+ * - Startup → models registered unconditionally (hardcoded)
12
+ */
13
+
14
+ import type { ExtensionAPI, ProviderModelConfig } from "@earendil-works/pi-coding-agent";
15
+ import type { OAuthCredentials, OAuthLoginCallbacks } from "@earendil-works/pi-ai";
16
+
17
+ const PROVIDER_ID = "ark-coding";
18
+ const PROVIDER_DISPLAY_NAME = "ARK Coding Plan";
19
+ const BASE_URL = "https://ark.cn-beijing.volces.com/api/coding/v3";
20
+
21
+ // ── Hardcoded models (parameters from models.dev) ─────────────────────────
22
+
23
+ const MODELS: ProviderModelConfig[] = [
24
+ { id: "deepseek-v3.2", name: "DeepSeek V3.2", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 163_840, maxTokens: 65_536, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
25
+ { id: "glm-4.7", name: "GLM 4.7", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 202_752, maxTokens: 131_072, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
26
+ { id: "glm-5.1", name: "GLM 5.1", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 202_752, maxTokens: 131_072, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
27
+ { id: "kimi-k2.5", name: "Kimi K2.5", reasoning: true, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262_144, maxTokens: 262_144, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
28
+ { id: "kimi-k2.6", name: "Kimi K2.6", reasoning: true, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262_144, maxTokens: 262_144, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
29
+ { id: "minimax-m2.5", name: "MiniMax M2.5", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 204_800, maxTokens: 131_072, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
30
+ { id: "minimax-m2.7", name: "MiniMax M2.7", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 196_608, maxTokens: 196_608, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
31
+ { id: "doubao-seed-2.0-code", name: "Doubao Seed 2.0 Code", reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 256_000, maxTokens: 128_000, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
32
+ { id: "doubao-seed-2.0-pro", name: "Doubao Seed 2.0 Pro", reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 256_000, maxTokens: 128_000, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
33
+ { id: "doubao-seed-2.0-lite", name: "Doubao Seed 2.0 Lite", reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 256_000, maxTokens: 32_000, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
34
+ { id: "doubao-seed-code", name: "Doubao Seed Code", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 256_000, maxTokens: 16_384, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
35
+ ];
36
+
37
+ // ── Entry ──────────────────────────────────────────────────────────────────
38
+
39
+ export function setupArkCoding(pi: ExtensionAPI) {
40
+ pi.registerProvider(PROVIDER_ID, {
41
+ name: PROVIDER_DISPLAY_NAME,
42
+ baseUrl: BASE_URL,
43
+ api: "openai-completions",
44
+ authHeader: true,
45
+ models: MODELS,
46
+ oauth: {
47
+ name: PROVIDER_DISPLAY_NAME,
48
+
49
+ async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
50
+ const apiKey = (await callbacks.onPrompt({
51
+ message: "Enter ARK Coding Plan API key:",
52
+ placeholder: "your-api-key",
53
+ })).trim();
54
+
55
+ if (!apiKey) throw new Error("API key cannot be empty.");
56
+
57
+ return {
58
+ refresh: apiKey,
59
+ access: apiKey,
60
+ expires: Date.now() + 1000 * 365.24 * 24 * 3600 * 1000, // ~1000 years
61
+ };
62
+ },
63
+
64
+ refreshToken(cred: OAuthCredentials): Promise<OAuthCredentials> {
65
+ return Promise.resolve(cred); // API key never expires
66
+ },
67
+
68
+ getApiKey(cred: OAuthCredentials): string {
69
+ return cred.access;
70
+ },
71
+ },
72
+ });
73
+ }
@@ -0,0 +1,9 @@
1
+ import { setupArkCoding } from "./ark-coding.js";
2
+ import { setupOllamaCloud } from "./ollama-cloud.js";
3
+ import { setupQianfanCoding } from "./qianfan-coding.js";
4
+
5
+ export function setupProviders(pi: any) {
6
+ setupArkCoding(pi);
7
+ setupOllamaCloud(pi);
8
+ setupQianfanCoding(pi);
9
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Ollama Cloud — OAuth/subscription provider with hardcoded models
3
+ *
4
+ * Provider: "ollama-cloud"
5
+ * Base URL: https://ollama.com/v1 (OpenAI-compatible)
6
+ * Auth: OAuth/subscription login → prompt for API key
7
+ *
8
+ * All models hardcoded from models.dev. No dynamic fetching, no config file caching.
9
+ * - No auth → no models in /model (clean UX, via hasConfiguredAuth)
10
+ * - Login → models become available immediately
11
+ * - Startup → models registered unconditionally (hardcoded)
12
+ */
13
+
14
+ import type { ExtensionAPI, ProviderModelConfig } from "@earendil-works/pi-coding-agent";
15
+ import type { OAuthCredentials, OAuthLoginCallbacks } from "@earendil-works/pi-ai";
16
+
17
+ const PROVIDER_ID = "ollama-cloud";
18
+ const PROVIDER_DISPLAY_NAME = "Ollama Cloud";
19
+ const BASE_URL = "https://ollama.com/v1";
20
+
21
+ // ── Hardcoded models (from models.dev) ────────────────────────────────────
22
+
23
+ const MODELS: ProviderModelConfig[] = [
24
+ { id: "cogito-2.1:671b", name: "cogito-2.1:671b", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 163_840, maxTokens: 32_000, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
25
+ { id: "deepseek-v3.1:671b", name: "deepseek-v3.1:671b", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 163_840, maxTokens: 163_840, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
26
+ { id: "deepseek-v3.2", name: "deepseek-v3.2", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 163_840, maxTokens: 65_536, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
27
+ { id: "deepseek-v4-flash", name: "deepseek-v4-flash", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 1_048_576, maxTokens: 1_048_576, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
28
+ { id: "deepseek-v4-pro", name: "deepseek-v4-pro", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 1_048_576, maxTokens: 1_048_576, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
29
+ { id: "devstral-2:123b", name: "devstral-2:123b", reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262_144, maxTokens: 262_144, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
30
+ { id: "devstral-small-2:24b", name: "devstral-small-2:24b", reasoning: false, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262_144, maxTokens: 262_144, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
31
+ { id: "gemini-3-flash-preview", name: "gemini-3-flash-preview", reasoning: true, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 1_048_576, maxTokens: 65_536, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
32
+ { id: "gemma3:12b", name: "gemma3:12b", reasoning: false, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 131_072, maxTokens: 131_072, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
33
+ { id: "gemma3:27b", name: "gemma3:27b", reasoning: false, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 131_072, maxTokens: 131_072, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
34
+ { id: "gemma3:4b", name: "gemma3:4b", reasoning: false, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 131_072, maxTokens: 131_072, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
35
+ { id: "gemma4:31b", name: "gemma4:31b", reasoning: true, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262_144, maxTokens: 262_144, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
36
+ { id: "glm-4.6", name: "glm-4.6", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 202_752, maxTokens: 131_072, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
37
+ { id: "glm-4.7", name: "glm-4.7", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 202_752, maxTokens: 131_072, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
38
+ { id: "glm-5", name: "glm-5", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 202_752, maxTokens: 131_072, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
39
+ { id: "glm-5.1", name: "glm-5.1", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 202_752, maxTokens: 131_072, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
40
+ { id: "gpt-oss:120b", name: "gpt-oss:120b", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 131_072, maxTokens: 32_768, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
41
+ { id: "gpt-oss:20b", name: "gpt-oss:20b", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 131_072, maxTokens: 32_768, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
42
+ { id: "kimi-k2-thinking", name: "kimi-k2-thinking", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262_144, maxTokens: 262_144, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
43
+ { id: "kimi-k2.5", name: "kimi-k2.5", reasoning: true, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262_144, maxTokens: 262_144, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
44
+ { id: "kimi-k2.6", name: "kimi-k2.6", reasoning: true, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262_144, maxTokens: 262_144, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
45
+ { id: "kimi-k2:1t", name: "kimi-k2:1t", reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262_144, maxTokens: 262_144, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
46
+ { id: "minimax-m2", name: "minimax-m2", reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 204_800, maxTokens: 128_000, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
47
+ { id: "minimax-m2.1", name: "minimax-m2.1", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 204_800, maxTokens: 131_072, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
48
+ { id: "minimax-m2.5", name: "minimax-m2.5", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 204_800, maxTokens: 131_072, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
49
+ { id: "minimax-m2.7", name: "minimax-m2.7", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 196_608, maxTokens: 196_608, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
50
+ { id: "ministral-3:14b", name: "ministral-3:14b", reasoning: false, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262_144, maxTokens: 128_000, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
51
+ { id: "ministral-3:3b", name: "ministral-3:3b", reasoning: false, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262_144, maxTokens: 128_000, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
52
+ { id: "ministral-3:8b", name: "ministral-3:8b", reasoning: false, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262_144, maxTokens: 128_000, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
53
+ { id: "mistral-large-3:675b", name: "mistral-large-3:675b", reasoning: false, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262_144, maxTokens: 262_144, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
54
+ { id: "nemotron-3-nano:30b", name: "nemotron-3-nano:30b", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 1_048_576, maxTokens: 131_072, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
55
+ { id: "nemotron-3-super", name: "nemotron-3-super", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262_144, maxTokens: 65_536, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
56
+ { id: "qwen3-coder-next", name: "qwen3-coder-next", reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262_144, maxTokens: 65_536, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
57
+ { id: "qwen3-coder:480b", name: "qwen3-coder:480b", reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262_144, maxTokens: 65_536, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
58
+ { id: "qwen3-next:80b", name: "qwen3-next:80b", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262_144, maxTokens: 32_768, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
59
+ { id: "qwen3-vl:235b", name: "qwen3-vl:235b", reasoning: true, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262_144, maxTokens: 32_768, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
60
+ { id: "qwen3-vl:235b-instruct", name: "qwen3-vl:235b-instruct", reasoning: false, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262_144, maxTokens: 131_072, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
61
+ { id: "qwen3.5:397b", name: "qwen3.5:397b", reasoning: true, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262_144, maxTokens: 65_536, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
62
+ { id: "rnj-1:8b", name: "rnj-1:8b", reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 32_768, maxTokens: 4_096, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
63
+ ];
64
+
65
+ // ── Entry ──────────────────────────────────────────────────────────────────
66
+
67
+ export function setupOllamaCloud(pi: ExtensionAPI) {
68
+ pi.registerProvider(PROVIDER_ID, {
69
+ name: PROVIDER_DISPLAY_NAME,
70
+ baseUrl: BASE_URL,
71
+ api: "openai-completions",
72
+ authHeader: true,
73
+ models: MODELS,
74
+ oauth: {
75
+ name: PROVIDER_DISPLAY_NAME,
76
+
77
+ async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
78
+ const apiKey = (await callbacks.onPrompt({
79
+ message: "Enter Ollama Cloud API key:",
80
+ placeholder: "your-api-key",
81
+ })).trim();
82
+
83
+ if (!apiKey) throw new Error("API key cannot be empty.");
84
+
85
+ return {
86
+ refresh: apiKey,
87
+ access: apiKey,
88
+ expires: Date.now() + 1000 * 365.24 * 24 * 3600 * 1000, // ~1000 years
89
+ };
90
+ },
91
+
92
+ refreshToken(cred: OAuthCredentials): Promise<OAuthCredentials> {
93
+ return Promise.resolve(cred); // API key never expires
94
+ },
95
+
96
+ getApiKey(cred: OAuthCredentials): string {
97
+ return cred.access;
98
+ },
99
+ },
100
+ });
101
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Baidu Qianfan Coding Plan — OAuth/subscription provider with hardcoded models
3
+ *
4
+ * Provider: "qianfan-coding"
5
+ * Base URL: https://qianfan.baidubce.com/v2/coding (OpenAI-compatible)
6
+ * Auth: OAuth/subscription login → prompt for API key
7
+ *
8
+ * All models hardcoded. No dynamic fetching, no config file caching.
9
+ * - No auth → no models in /model (clean UX, via hasConfiguredAuth)
10
+ * - Login → models become available immediately
11
+ * - Startup → models registered unconditionally (hardcoded)
12
+ */
13
+
14
+ import type { ExtensionAPI, ProviderModelConfig } from "@earendil-works/pi-coding-agent";
15
+ import type { OAuthCredentials, OAuthLoginCallbacks } from "@earendil-works/pi-ai";
16
+
17
+ const PROVIDER_ID = "qianfan-coding";
18
+ const PROVIDER_DISPLAY_NAME = "Baidu Qianfan Coding Plan";
19
+ const BASE_URL = "https://qianfan.baidubce.com/v2/coding";
20
+
21
+ // ── Hardcoded models (parameters from models.dev + Baidu docs) ────────────
22
+
23
+ const MODELS: ProviderModelConfig[] = [
24
+ { id: "deepseek-v3.2", name: "DeepSeek V3.2", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 163_840, maxTokens: 65_536, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
25
+ { id: "glm-4.7", name: "GLM 4.7", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 202_752, maxTokens: 131_072, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
26
+ { id: "glm-5", name: "GLM 5", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 202_752, maxTokens: 131_072, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
27
+ { id: "glm-5.1", name: "GLM 5.1", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 202_752, maxTokens: 131_072, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
28
+ { id: "kimi-k2.5", name: "Kimi K2.5", reasoning: true, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 262_144, maxTokens: 262_144, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
29
+ { id: "minimax-m2.1", name: "MiniMax M2.1", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 204_800, maxTokens: 131_072, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
30
+ { id: "minimax-m2.5", name: "MiniMax M2.5", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 204_800, maxTokens: 131_072, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
31
+ { id: "ernie-4.5-turbo-20260402", name: "ERNIE 4.5 Turbo", reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 128_000, maxTokens: 12_288, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
32
+ { id: "deepseek-v4-flash", name: "DeepSeek V4 Flash", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 1_048_576, maxTokens: 1_048_576, compat: { supportsDeveloperRole: false, supportsReasoningEffort: true } as any },
33
+ ];
34
+
35
+ // ── Entry ──────────────────────────────────────────────────────────────────
36
+
37
+ export function setupQianfanCoding(pi: ExtensionAPI) {
38
+ pi.registerProvider(PROVIDER_ID, {
39
+ name: PROVIDER_DISPLAY_NAME,
40
+ baseUrl: BASE_URL,
41
+ api: "openai-completions",
42
+ authHeader: true,
43
+ models: MODELS,
44
+ oauth: {
45
+ name: PROVIDER_DISPLAY_NAME,
46
+
47
+ async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
48
+ const apiKey = (await callbacks.onPrompt({
49
+ message: "Enter Baidu Qianfan Coding Plan API key:",
50
+ placeholder: "your-api-key",
51
+ })).trim();
52
+
53
+ if (!apiKey) throw new Error("API key cannot be empty.");
54
+
55
+ return {
56
+ refresh: apiKey,
57
+ access: apiKey,
58
+ expires: Date.now() + 1000 * 365.24 * 24 * 3600 * 1000, // ~1000 years
59
+ };
60
+ },
61
+
62
+ refreshToken(cred: OAuthCredentials): Promise<OAuthCredentials> {
63
+ return Promise.resolve(cred); // API key never expires
64
+ },
65
+
66
+ getApiKey(cred: OAuthCredentials): string {
67
+ return cred.access;
68
+ },
69
+ },
70
+ });
71
+ }
@@ -20,6 +20,7 @@ import { resolve } from "node:path";
20
20
  const DANGEROUS_COMMANDS: [string, string[]][] = [
21
21
  ["rm", []],
22
22
  ["sudo", []],
23
+ ["npm", ["publish"]],
23
24
  ["svn", ["commit", "revert"]],
24
25
  ["git", ["reset", "restore", "clean", "push", "revert"]],
25
26
  ];
@@ -8,12 +8,27 @@ import * as path from "node:path";
8
8
  import * as os from "node:os";
9
9
  import type { Model } from "@earendil-works/pi-ai";
10
10
 
11
- const CONFIG_DIR = path.join(os.homedir(), ".pi", "agent", "extensions");
11
+ const CONFIG_DIR = path.join(os.homedir(), ".pi", "agent");
12
12
  const CONFIG_FILE = path.join(CONFIG_DIR, "decorated-pi.json");
13
13
 
14
+ export interface ProviderModelEntry {
15
+ id: string;
16
+ name: string;
17
+ reasoning: boolean;
18
+ contextWindow: number;
19
+ maxTokens: number;
20
+ input: ("text" | "image")[];
21
+ }
22
+
23
+ export interface ProviderCache {
24
+ lastSynced?: string;
25
+ models: ProviderModelEntry[];
26
+ }
27
+
14
28
  export interface DecoratedPiConfig {
15
29
  imageModelKey?: string | null;
16
30
  compactModelKey?: string | null;
31
+ providers?: Record<string, ProviderCache>;
17
32
  }
18
33
 
19
34
  export function loadConfig(): DecoratedPiConfig {
@@ -41,6 +56,28 @@ export function parseModelKey(key: string): { provider: string; modelId: string
41
56
  return { provider: key.slice(0, i), modelId: key.slice(i + 1) };
42
57
  }
43
58
 
59
+ // ─── Provider ────────────────────────────────────────────────────────────────
60
+
61
+ export function loadProvider(name: string): ProviderCache | null {
62
+ return loadConfig().providers?.[name] ?? null;
63
+ }
64
+
65
+ export function saveProvider(name: string, data: ProviderCache) {
66
+ if (!fs.existsSync(CONFIG_DIR)) fs.mkdirSync(CONFIG_DIR, { recursive: true });
67
+ const current = loadConfig();
68
+ if (!current.providers) current.providers = {};
69
+ current.providers[name] = data;
70
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(current, null, 2) + "\n", "utf-8");
71
+ }
72
+
73
+ export function removeProvider(name: string) {
74
+ const current = loadConfig();
75
+ if (current.providers?.[name]) {
76
+ delete current.providers[name];
77
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(current, null, 2) + "\n", "utf-8");
78
+ }
79
+ }
80
+
44
81
  // ─── Getter ─────────────────────────────────────────────────────────────────
45
82
 
46
83
  export function getImageModelKey(): string | null {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "decorated-pi",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Essential utilities for pi: safety gates, secret redaction, smart @ completion, dynamic AGENTS loading, image fallback, and LSP tools",
5
5
  "keywords": [
6
6
  "pi",
@@ -30,6 +30,7 @@
30
30
  "openai": "^6.37.0"
31
31
  },
32
32
  "peerDependencies": {
33
+ "@earendil-works/pi-ai": "*",
33
34
  "@earendil-works/pi-coding-agent": "*",
34
35
  "@earendil-works/pi-tui": "*",
35
36
  "typebox": "*"