@cirthan/pi-cirthan-provider 0.1.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/LICENSE +21 -0
- package/README.md +42 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +175 -0
- package/package.json +37 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cirthan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# pi-cirthan-provider
|
|
2
|
+
|
|
3
|
+
Cirthan model provider for [pi](https://github.com/mariozechner/pi). Registers the `cirthan` provider using Cirthan's OpenAI-compatible API.
|
|
4
|
+
|
|
5
|
+
This package fetches models from Cirthan's `/v1/models` endpoint at session start to filter enabled models.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
Provide an API key via environment variable or `~/.pi/agent/auth.json`.
|
|
10
|
+
|
|
11
|
+
### Option 1: Environment variable
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
export CIRTHAN_API_KEY="..."
|
|
15
|
+
# optional
|
|
16
|
+
export CIRTHAN_BASE_URL="https://api.cirthan.com/v1"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Option 2: auth.json (recommended)
|
|
20
|
+
|
|
21
|
+
Add to `~/.pi/agent/auth.json`:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"cirthan": {
|
|
26
|
+
"type": "api_key",
|
|
27
|
+
"key": "your_key_here"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
- List/switch models: `pi /model`
|
|
35
|
+
- Use default model: `pi --model cirthan`
|
|
36
|
+
- Use a specific model: `pi --model cirthan:glm-4.7-flash`
|
|
37
|
+
|
|
38
|
+
## Default model
|
|
39
|
+
|
|
40
|
+
The default model for this provider is:
|
|
41
|
+
|
|
42
|
+
- `glm-4.7-flash`
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cirthan Provider Extension
|
|
3
|
+
*
|
|
4
|
+
* Cirthan provider for pi.
|
|
5
|
+
*
|
|
6
|
+
* - Registers a provider using Cirthan's OpenAI-compatible API
|
|
7
|
+
* - Fetches /v1/models on session start to filter which models are enabled
|
|
8
|
+
* - Provides hardcoded model configs with metadata
|
|
9
|
+
*/
|
|
10
|
+
import { type ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
11
|
+
export default function (pi: ExtensionAPI): void;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cirthan Provider Extension
|
|
3
|
+
*
|
|
4
|
+
* Cirthan provider for pi.
|
|
5
|
+
*
|
|
6
|
+
* - Registers a provider using Cirthan's OpenAI-compatible API
|
|
7
|
+
* - Fetches /v1/models on session start to filter which models are enabled
|
|
8
|
+
* - Provides hardcoded model configs with metadata
|
|
9
|
+
*/
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// Configuration
|
|
12
|
+
// =============================================================================
|
|
13
|
+
const CIRTHAN_API_BASE_URL = (process.env.CIRTHAN_BASE_URL ?? "https://api.cirthan.com/v1").replace(/\/+$/, "");
|
|
14
|
+
const CIRTHAN_MODELS_ENDPOINT = `${CIRTHAN_API_BASE_URL}/models`;
|
|
15
|
+
/** Default model for this provider. */
|
|
16
|
+
const CIRTHAN_DEFAULT_MODEL_ID = "glm-4.7-flash";
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// Hardcoded model configs
|
|
19
|
+
// =============================================================================
|
|
20
|
+
/** Hardcoded model configurations - exactly matching /v1/models response. */
|
|
21
|
+
const HARDCODED_MODELS = [
|
|
22
|
+
{
|
|
23
|
+
id: "glm-4.7-flash",
|
|
24
|
+
name: "glm-4.7-flash",
|
|
25
|
+
reasoning: true,
|
|
26
|
+
input: ["text"],
|
|
27
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
28
|
+
contextWindow: 200000,
|
|
29
|
+
maxTokens: 131072,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "qwen3-vl-8b-instruct",
|
|
33
|
+
name: "qwen3-vl-8b-instruct",
|
|
34
|
+
reasoning: false,
|
|
35
|
+
input: ["text", "image"],
|
|
36
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
37
|
+
contextWindow: 131072,
|
|
38
|
+
maxTokens: 32768,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: "minimax-m2.5",
|
|
42
|
+
name: "minimax-m2.5",
|
|
43
|
+
reasoning: true,
|
|
44
|
+
input: ["text"],
|
|
45
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
46
|
+
contextWindow: 196608,
|
|
47
|
+
maxTokens: 131072,
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
// =============================================================================
|
|
51
|
+
// Auth helpers
|
|
52
|
+
// =============================================================================
|
|
53
|
+
async function getCirthanApiKey(ctx) {
|
|
54
|
+
const envKey = process.env.CIRTHAN_API_KEY;
|
|
55
|
+
try {
|
|
56
|
+
const authKey = await ctx.modelRegistry.getApiKeyForProvider("cirthan");
|
|
57
|
+
if (authKey)
|
|
58
|
+
return authKey;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Provider may not be registered yet.
|
|
62
|
+
}
|
|
63
|
+
return envKey;
|
|
64
|
+
}
|
|
65
|
+
async function hasCirthanApiKey(ctx) {
|
|
66
|
+
if (process.env.CIRTHAN_API_KEY)
|
|
67
|
+
return true;
|
|
68
|
+
try {
|
|
69
|
+
const authKey = await ctx.modelRegistry.getApiKeyForProvider("cirthan");
|
|
70
|
+
return !!authKey;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// =============================================================================
|
|
77
|
+
// Model fetching + filtering
|
|
78
|
+
// =============================================================================
|
|
79
|
+
/**
|
|
80
|
+
* Verify models are enabled by fetching /v1/models.
|
|
81
|
+
* Returns hardcoded configs filtered to only include enabled models.
|
|
82
|
+
*/
|
|
83
|
+
async function fetchAndFilterModels(apiKey) {
|
|
84
|
+
const controller = new AbortController();
|
|
85
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
86
|
+
try {
|
|
87
|
+
const headers = {
|
|
88
|
+
Accept: "application/json",
|
|
89
|
+
};
|
|
90
|
+
if (apiKey)
|
|
91
|
+
headers.Authorization = `Bearer ${apiKey}`;
|
|
92
|
+
console.log(`[Cirthan Provider] Fetching enabled models from: ${CIRTHAN_MODELS_ENDPOINT}`);
|
|
93
|
+
const response = await fetch(CIRTHAN_MODELS_ENDPOINT, { headers, signal: controller.signal });
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
const errorText = await response.text();
|
|
96
|
+
throw new Error(`Failed to fetch models: ${response.status} ${response.statusText} - ${errorText}`);
|
|
97
|
+
}
|
|
98
|
+
const data = (await response.json());
|
|
99
|
+
// Build map of model ID to context window from API
|
|
100
|
+
const apiModelContexts = new Map();
|
|
101
|
+
for (const model of data.data ?? []) {
|
|
102
|
+
apiModelContexts.set(model.id.toLowerCase(), model.max_model_len ?? model.contextWindow ?? 0);
|
|
103
|
+
}
|
|
104
|
+
console.log(`[Cirthan Provider] API returned ${apiModelContexts.size} enabled models`);
|
|
105
|
+
// Filter: only include models that are enabled in the API
|
|
106
|
+
// Always include default model regardless of API response
|
|
107
|
+
// Use case-insensitive comparison since API may return different casing
|
|
108
|
+
const filtered = HARDCODED_MODELS.filter((model) => {
|
|
109
|
+
if (model.id === CIRTHAN_DEFAULT_MODEL_ID)
|
|
110
|
+
return true;
|
|
111
|
+
return apiModelContexts.has(model.id.toLowerCase());
|
|
112
|
+
}).map((model) => {
|
|
113
|
+
// Override contextWindow with API's max_model_len if available
|
|
114
|
+
const apiContext = apiModelContexts.get(model.id.toLowerCase());
|
|
115
|
+
if (apiContext && apiContext > 0) {
|
|
116
|
+
return { ...model, contextWindow: apiContext };
|
|
117
|
+
}
|
|
118
|
+
return model;
|
|
119
|
+
});
|
|
120
|
+
// Sort with default model first, then alphabetical
|
|
121
|
+
filtered.sort((a, b) => {
|
|
122
|
+
if (a.id === CIRTHAN_DEFAULT_MODEL_ID)
|
|
123
|
+
return -1;
|
|
124
|
+
if (b.id === CIRTHAN_DEFAULT_MODEL_ID)
|
|
125
|
+
return 1;
|
|
126
|
+
return a.id.localeCompare(b.id);
|
|
127
|
+
});
|
|
128
|
+
console.log(`[Cirthan Provider] Registered ${filtered.length} model configs for provider 'cirthan'`);
|
|
129
|
+
return filtered;
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
133
|
+
if (message === "AbortError" || message.includes("aborted")) {
|
|
134
|
+
console.warn("[Cirthan Provider] Model fetch timed out after 5s");
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
console.error("[Cirthan Provider] Failed to fetch models:", error);
|
|
138
|
+
}
|
|
139
|
+
// On error, return all hardcoded models as fallback
|
|
140
|
+
console.log("[Cirthan Provider] Using all hardcoded models as fallback");
|
|
141
|
+
return [...HARDCODED_MODELS];
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
clearTimeout(timeout);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// =============================================================================
|
|
148
|
+
// Extension entry point
|
|
149
|
+
// =============================================================================
|
|
150
|
+
export default function (pi) {
|
|
151
|
+
// Initial registration must happen synchronously (see synthetic provider).
|
|
152
|
+
pi.registerProvider("cirthan", {
|
|
153
|
+
baseUrl: CIRTHAN_API_BASE_URL,
|
|
154
|
+
apiKey: "CIRTHAN_API_KEY",
|
|
155
|
+
api: "openai-completions",
|
|
156
|
+
models: HARDCODED_MODELS,
|
|
157
|
+
});
|
|
158
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
159
|
+
const hasKey = await hasCirthanApiKey(ctx);
|
|
160
|
+
const apiKey = await getCirthanApiKey(ctx);
|
|
161
|
+
if (!hasKey) {
|
|
162
|
+
console.log("[Cirthan Provider] API key not configured.");
|
|
163
|
+
console.log("[Cirthan Provider] Options:");
|
|
164
|
+
console.log(" 1. Set CIRTHAN_API_KEY environment variable");
|
|
165
|
+
console.log(" 2. Add to ~/.pi/agent/auth.json (provider: \"cirthan\")");
|
|
166
|
+
}
|
|
167
|
+
const models = await fetchAndFilterModels(apiKey);
|
|
168
|
+
ctx.modelRegistry.registerProvider("cirthan", {
|
|
169
|
+
baseUrl: CIRTHAN_API_BASE_URL,
|
|
170
|
+
apiKey: "CIRTHAN_API_KEY",
|
|
171
|
+
api: "openai-completions",
|
|
172
|
+
models,
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cirthan/pi-cirthan-provider",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Pi provider package for the Cirthan API.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist/**/*",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"keywords": [
|
|
13
|
+
"pi",
|
|
14
|
+
"pi-package",
|
|
15
|
+
"extension"
|
|
16
|
+
],
|
|
17
|
+
"pi": {
|
|
18
|
+
"extensions": ["./dist/index.js"]
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"clean": "rm -rf dist",
|
|
22
|
+
"build": "tsc -p tsconfig.build.json",
|
|
23
|
+
"dev": "tsc -p tsconfig.build.json --watch",
|
|
24
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@mariozechner/pi-coding-agent": "*"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^22.10.7",
|
|
31
|
+
"eslint": "^9.17.0",
|
|
32
|
+
"globals": "^17.3.0",
|
|
33
|
+
"typescript": "^5.7.3",
|
|
34
|
+
"typescript-eslint": "^8.56.0"
|
|
35
|
+
},
|
|
36
|
+
"license": "MIT"
|
|
37
|
+
}
|