tokenfactory-pi 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/.idea/inspectionProfiles/profiles_settings.xml +5 -0
- package/LICENSE +21 -0
- package/README.md +39 -0
- package/index.ts +146 -0
- package/package.json +34 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Dmitry Orlov
|
|
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,39 @@
|
|
|
1
|
+
# tokenfactory-pi
|
|
2
|
+
|
|
3
|
+
[Nebius Token Factory](https://tokenfactory.nebius.com/) provider extension for [pi coding agent](https://pi.dev).
|
|
4
|
+
|
|
5
|
+
Fetches the current model catalog from the Token Factory API on startup and registers all tool-capable models. No changes to pi-mono required.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Get an API key from https://tokenfactory.nebius.com/
|
|
11
|
+
export NEBIUS_API_KEY=your-key-here
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Load the extension
|
|
18
|
+
pi -e tokenfactory-pi
|
|
19
|
+
|
|
20
|
+
# Specify provider explicitly
|
|
21
|
+
pi -e tokenfactory-pi --provider nebius
|
|
22
|
+
|
|
23
|
+
# Pick a specific model
|
|
24
|
+
pi -e tokenfactory-pi --provider nebius --model Qwen/Qwen3-32B
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Once running, use `/nebius-models` to list all available models.
|
|
28
|
+
|
|
29
|
+
## How it works
|
|
30
|
+
|
|
31
|
+
On startup the extension:
|
|
32
|
+
|
|
33
|
+
1. Reads `NEBIUS_API_KEY` from environment (no-op if missing)
|
|
34
|
+
2. Fetches `GET /v1/models?verbose=true` from the Token Factory API
|
|
35
|
+
3. Filters for models with `tools` support and `->text` output modality
|
|
36
|
+
4. Registers them as the `nebius` provider via `pi.registerProvider()`
|
|
37
|
+
|
|
38
|
+
All models use the `openai-completions` API with
|
|
39
|
+
`compat: { supportsDeveloperRole: false, maxTokensField: "max_tokens" }`.
|
package/index.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nebius Token Factory — pi extension
|
|
3
|
+
*
|
|
4
|
+
* Fetches the current model catalog from the Token Factory API on startup
|
|
5
|
+
* and registers all tool-capable text-generation models as a "nebius" provider.
|
|
6
|
+
*
|
|
7
|
+
* Environment:
|
|
8
|
+
* NEBIUS_API_KEY — required, Token Factory API key
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* pi -e /path/to/tokenfactory-pi
|
|
12
|
+
* pi -e /path/to/tokenfactory-pi --provider nebius
|
|
13
|
+
* pi -e /path/to/tokenfactory-pi --provider nebius --model Qwen/Qwen3-32B
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
17
|
+
|
|
18
|
+
const PROVIDER_NAME = "nebius";
|
|
19
|
+
const BASE_URL = "https://api.tokenfactory.nebius.com/v1";
|
|
20
|
+
const ENV_VAR = "NEBIUS_API_KEY";
|
|
21
|
+
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Token Factory API types
|
|
24
|
+
// ============================================================================
|
|
25
|
+
|
|
26
|
+
interface TokenFactoryModel {
|
|
27
|
+
id: string;
|
|
28
|
+
name?: string;
|
|
29
|
+
context_length?: number;
|
|
30
|
+
supported_features?: string[];
|
|
31
|
+
architecture?: { modality?: string };
|
|
32
|
+
pricing?: { prompt?: string; completion?: string };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface TokenFactoryResponse {
|
|
36
|
+
data: TokenFactoryModel[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ============================================================================
|
|
40
|
+
// Helpers
|
|
41
|
+
// ============================================================================
|
|
42
|
+
|
|
43
|
+
function isToolCapableTextModel(m: TokenFactoryModel): boolean {
|
|
44
|
+
const features = m.supported_features || [];
|
|
45
|
+
const modality = m.architecture?.modality || "";
|
|
46
|
+
return features.includes("tools") && modality.includes("->text");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function parseInputModalities(modality: string): ("text" | "image")[] {
|
|
50
|
+
const input: ("text" | "image")[] = ["text"];
|
|
51
|
+
if (modality.includes("image")) input.push("image");
|
|
52
|
+
return input;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function parseCostPerMillion(raw: string | undefined): number {
|
|
56
|
+
return parseFloat(raw || "0") * 1_000_000;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isReasoningModel(id: string): boolean {
|
|
60
|
+
return /(-R1|-Thinking|QwQ)/.test(id);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ============================================================================
|
|
64
|
+
// Extension entry point
|
|
65
|
+
// ============================================================================
|
|
66
|
+
|
|
67
|
+
export default async function (pi: ExtensionAPI) {
|
|
68
|
+
const apiKey = process.env[ENV_VAR];
|
|
69
|
+
if (!apiKey) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let response: TokenFactoryResponse;
|
|
74
|
+
try {
|
|
75
|
+
const res = await fetch(`${BASE_URL}/models?verbose=true`, {
|
|
76
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
77
|
+
});
|
|
78
|
+
if (!res.ok) {
|
|
79
|
+
console.warn(`[${PROVIDER_NAME}] API returned ${res.status}: ${res.statusText}`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
response = (await res.json()) as TokenFactoryResponse;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.warn(`[${PROVIDER_NAME}] Failed to fetch models:`, error);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!Array.isArray(response.data)) {
|
|
89
|
+
console.warn(`[${PROVIDER_NAME}] Unexpected API response shape`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const models = [];
|
|
94
|
+
for (const m of response.data) {
|
|
95
|
+
if (!isToolCapableTextModel(m)) continue;
|
|
96
|
+
|
|
97
|
+
const modality = m.architecture?.modality || "";
|
|
98
|
+
|
|
99
|
+
models.push({
|
|
100
|
+
id: m.id,
|
|
101
|
+
name: m.name || m.id,
|
|
102
|
+
reasoning: isReasoningModel(m.id),
|
|
103
|
+
input: parseInputModalities(modality),
|
|
104
|
+
cost: {
|
|
105
|
+
input: parseCostPerMillion(m.pricing?.prompt),
|
|
106
|
+
output: parseCostPerMillion(m.pricing?.completion),
|
|
107
|
+
cacheRead: 0,
|
|
108
|
+
cacheWrite: 0,
|
|
109
|
+
},
|
|
110
|
+
contextWindow: m.context_length || 131072,
|
|
111
|
+
maxTokens: Math.min(m.context_length || 32768, 32768),
|
|
112
|
+
compat: {
|
|
113
|
+
supportsDeveloperRole: false,
|
|
114
|
+
maxTokensField: "max_tokens" as const,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
pi.registerProvider(PROVIDER_NAME, {
|
|
120
|
+
baseUrl: BASE_URL,
|
|
121
|
+
apiKey: ENV_VAR,
|
|
122
|
+
api: "openai-completions",
|
|
123
|
+
models,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// /nebius-models command to list and select a model
|
|
127
|
+
pi.registerCommand("nebius-models", {
|
|
128
|
+
description: "List available Nebius Token Factory models",
|
|
129
|
+
handler: async (_args, ctx) => {
|
|
130
|
+
if (models.length === 0) {
|
|
131
|
+
ctx.ui.notify("No Nebius models available", "warning");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const items = models
|
|
135
|
+
.sort((a, b) => a.id.localeCompare(b.id))
|
|
136
|
+
.map((m) => {
|
|
137
|
+
const tags = [];
|
|
138
|
+
if (m.reasoning) tags.push("reasoning");
|
|
139
|
+
if (m.input.includes("image")) tags.push("vision");
|
|
140
|
+
const suffix = tags.length > 0 ? ` (${tags.join(", ")})` : "";
|
|
141
|
+
return `${m.id}${suffix}`;
|
|
142
|
+
});
|
|
143
|
+
await ctx.ui.select(`Nebius Token Factory — ${models.length} models`, items);
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tokenfactory-pi",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Nebius Token Factory provider extension for pi coding agent",
|
|
5
|
+
"main": "index.ts",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/mosquito/tokenfactory-pi.git"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"pi",
|
|
14
|
+
"pi-extension",
|
|
15
|
+
"pi-package",
|
|
16
|
+
"nebius",
|
|
17
|
+
"tokenfactory",
|
|
18
|
+
"llm",
|
|
19
|
+
"coding-agent"
|
|
20
|
+
],
|
|
21
|
+
"pi": {
|
|
22
|
+
"extensions": [
|
|
23
|
+
"./index.ts"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@mariozechner/pi-coding-agent": "*"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"clean": "echo 'nothing to clean'",
|
|
31
|
+
"build": "echo 'nothing to build'",
|
|
32
|
+
"check": "echo 'nothing to check'"
|
|
33
|
+
}
|
|
34
|
+
}
|