tokenfactory-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.
@@ -0,0 +1,16 @@
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
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
16
+ export default function (pi: ExtensionAPI): Promise<void>;
package/dist/index.js ADDED
@@ -0,0 +1,117 @@
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
+ const PROVIDER_NAME = "nebius";
16
+ const BASE_URL = "https://api.tokenfactory.nebius.com/v1";
17
+ const ENV_VAR = "NEBIUS_API_KEY";
18
+ // ============================================================================
19
+ // Helpers
20
+ // ============================================================================
21
+ function isToolCapableTextModel(m) {
22
+ const features = m.supported_features || [];
23
+ const modality = m.architecture?.modality || "";
24
+ return features.includes("tools") && modality.includes("->text");
25
+ }
26
+ function parseInputModalities(modality) {
27
+ const input = ["text"];
28
+ if (modality.includes("image"))
29
+ input.push("image");
30
+ return input;
31
+ }
32
+ function parseCostPerMillion(raw) {
33
+ return parseFloat(raw || "0") * 1_000_000;
34
+ }
35
+ function isReasoningModel(id) {
36
+ return /(-R1|-Thinking|QwQ)/.test(id);
37
+ }
38
+ // ============================================================================
39
+ // Extension entry point
40
+ // ============================================================================
41
+ export default async function (pi) {
42
+ const apiKey = process.env[ENV_VAR];
43
+ if (!apiKey) {
44
+ return;
45
+ }
46
+ let response;
47
+ try {
48
+ const res = await fetch(`${BASE_URL}/models?verbose=true`, {
49
+ headers: { Authorization: `Bearer ${apiKey}` },
50
+ });
51
+ if (!res.ok) {
52
+ console.warn(`[${PROVIDER_NAME}] API returned ${res.status}: ${res.statusText}`);
53
+ return;
54
+ }
55
+ response = (await res.json());
56
+ }
57
+ catch (error) {
58
+ console.warn(`[${PROVIDER_NAME}] Failed to fetch models:`, error);
59
+ return;
60
+ }
61
+ if (!Array.isArray(response.data)) {
62
+ console.warn(`[${PROVIDER_NAME}] Unexpected API response shape`);
63
+ return;
64
+ }
65
+ const models = [];
66
+ for (const m of response.data) {
67
+ if (!isToolCapableTextModel(m))
68
+ continue;
69
+ const modality = m.architecture?.modality || "";
70
+ models.push({
71
+ id: m.id,
72
+ name: m.name || m.id,
73
+ reasoning: isReasoningModel(m.id),
74
+ input: parseInputModalities(modality),
75
+ cost: {
76
+ input: parseCostPerMillion(m.pricing?.prompt),
77
+ output: parseCostPerMillion(m.pricing?.completion),
78
+ cacheRead: 0,
79
+ cacheWrite: 0,
80
+ },
81
+ contextWindow: m.context_length || 131072,
82
+ maxTokens: Math.min(m.context_length || 32768, 32768),
83
+ compat: {
84
+ supportsDeveloperRole: false,
85
+ maxTokensField: "max_tokens",
86
+ },
87
+ });
88
+ }
89
+ pi.registerProvider(PROVIDER_NAME, {
90
+ baseUrl: BASE_URL,
91
+ apiKey: ENV_VAR,
92
+ api: "openai-completions",
93
+ models,
94
+ });
95
+ // /nebius-models command to list and select a model
96
+ pi.registerCommand("nebius-models", {
97
+ description: "List available Nebius Token Factory models",
98
+ handler: async (_args, ctx) => {
99
+ if (models.length === 0) {
100
+ ctx.ui.notify("No Nebius models available", "warning");
101
+ return;
102
+ }
103
+ const items = models
104
+ .sort((a, b) => a.id.localeCompare(b.id))
105
+ .map((m) => {
106
+ const tags = [];
107
+ if (m.reasoning)
108
+ tags.push("reasoning");
109
+ if (m.input.includes("image"))
110
+ tags.push("vision");
111
+ const suffix = tags.length > 0 ? ` (${tags.join(", ")})` : "";
112
+ return `${m.id}${suffix}`;
113
+ });
114
+ await ctx.ui.select(`Nebius Token Factory — ${models.length} models`, items);
115
+ },
116
+ });
117
+ }
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "tokenfactory-pi",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Nebius Token Factory provider extension for pi coding agent",
5
- "main": "index.ts",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
6
7
  "type": "module",
7
8
  "license": "MIT",
8
9
  "repository": {
@@ -20,15 +21,19 @@
20
21
  ],
21
22
  "pi": {
22
23
  "extensions": [
23
- "./index.ts"
24
+ "./dist/index.js"
24
25
  ]
25
26
  },
26
27
  "peerDependencies": {
27
28
  "@mariozechner/pi-coding-agent": "*"
28
29
  },
30
+ "devDependencies": {
31
+ "@types/node": "^20.0.0",
32
+ "typescript": "^5.9.3"
33
+ },
29
34
  "scripts": {
30
- "clean": "echo 'nothing to clean'",
31
- "build": "echo 'nothing to build'",
32
- "check": "echo 'nothing to check'"
35
+ "clean": "rm -rf dist",
36
+ "build": "tsc",
37
+ "check": "tsc --noEmit"
33
38
  }
34
- }
39
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "./dist",
7
+ "rootDir": "./",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": true,
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true
15
+ },
16
+ "include": ["*.ts"],
17
+ "exclude": ["node_modules", "dist"]
18
+ }
@@ -1,5 +0,0 @@
1
- <component name="InspectionProjectProfileManager">
2
- <settings>
3
- <option name="PROJECT_PROFILE" />
4
- </settings>
5
- </component>
package/index.ts DELETED
@@ -1,146 +0,0 @@
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
- }