@ai-for-dev/providers 0.2.0 → 0.3.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/README.md CHANGED
@@ -40,6 +40,17 @@ Seuls `qwen-3.8-27b` et `ARES/DeepSeek-V4.1-Flash` lisent un niveau d'effort dan
40
40
 
41
41
  Un modèle marqué `hidden: true` n'apparaît pas, même si le serveur le liste. Un modèle qui n'est pas dans la table reste utilisable. Il prend la taille de contexte donnée par le serveur (128 000 tokens à défaut), 32 768 tokens de sortie, sans images ni raisonnement.
42
42
 
43
+ ## Exporter un `models.json`
44
+
45
+ Certains outils lisent un `models.json` mais ne chargent pas d'extension. C'est le cas de trysquare, qui lance pi sans extension et relaie la clé lui-même. Pour eux, le paquet écrit ses deux fournisseurs au format de `~/.pi/agent/models.json` :
46
+
47
+ ```bash
48
+ npm ci
49
+ npm run export-models -- chemin/vers/models.json # sans chemin, le fichier sort sur stdout
50
+ ```
51
+
52
+ L'export lit seulement la table de `extensions/index.ts`, sans appeler les serveurs : une même version du paquet écrit toujours le même fichier. Les modèles marqués `hidden` sont exclus, et un modèle sans `contextWindow` dans la table fait échouer l'export. Les clés restent des références (`$ILAAS_API_KEY`, `$IA4DEV_API_KEY`) que pi lit dans l'environnement. `IA4DEV_ILAAS_BASE_URL` et `IA4DEV_GRICAD_BASE_URL` changent l'URL écrite, comme pour l'extension.
53
+
43
54
  ## Limites
44
55
 
45
56
  - Un fournisseur déjà défini sous le même nom dans `~/.pi/agent/models.json` prend le dessus sur celui du paquet.
@@ -36,7 +36,7 @@ interface ModelTuning {
36
36
  hidden?: boolean;
37
37
  }
38
38
 
39
- interface Endpoint {
39
+ export interface Endpoint {
40
40
  id: string;
41
41
  name: string;
42
42
  baseUrl: string;
@@ -44,7 +44,7 @@ interface Endpoint {
44
44
  models: Record<string, ModelTuning>;
45
45
  }
46
46
 
47
- const ENDPOINTS: Endpoint[] = [
47
+ export const ENDPOINTS: Endpoint[] = [
48
48
  {
49
49
  id: "ia4dev-ilaas",
50
50
  name: "ILAAS",
@@ -70,10 +70,10 @@ const ENDPOINTS: Endpoint[] = [
70
70
  envVar: "IA4DEV_API_KEY",
71
71
  models: {
72
72
  // The chat template accepts low, medium and xhigh (its default).
73
- "qwen-3.8-27b": { vision: true, thinking: { low: "low", medium: "medium", high: "xhigh" } },
73
+ "qwen-3.8-27b": { contextWindow: 262_144, vision: true, thinking: { low: "low", medium: "medium", high: "xhigh" } },
74
74
  // The deployed chat template accepts low, high (its default) and max, but not medium.
75
- "ARES/DeepSeek-V4.1-Flash": { vision: true, thinking: { low: "low", medium: "high", high: "max" } },
76
- "gemma-4-31b": { vision: true, thinking: "toggle" },
75
+ "ARES/DeepSeek-V4.1-Flash": { contextWindow: 1_048_576, vision: true, thinking: { low: "low", medium: "high", high: "max" } },
76
+ "gemma-4-31b": { contextWindow: 159_488, vision: true, thinking: "toggle" },
77
77
  },
78
78
  },
79
79
  ];
@@ -121,7 +121,7 @@ interface RemoteModel {
121
121
  max_output_tokens?: number;
122
122
  }
123
123
 
124
- function toModel(endpoint: Endpoint, remote: RemoteModel): ChatModel {
124
+ export function toModel(endpoint: Endpoint, remote: RemoteModel): ChatModel {
125
125
  const tuning = endpoint.models[remote.id] ?? {};
126
126
  const reasoning = reasoningFor(tuning.thinking ?? "none");
127
127
  const contextWindow =
@@ -169,11 +169,47 @@ async function fetchModels(endpoint: Endpoint, context: RefreshModelsContext): P
169
169
  .map((m) => toModel(endpoint, m));
170
170
  }
171
171
 
172
+ // <ID>_BASE_URL (ex: IA4DEV_ILAAS_BASE_URL) remplace l'URL par défaut, pour tester ou changer de serveur.
173
+ function withBaseUrl(base: Endpoint): Endpoint {
174
+ const urlVar = `${base.id.toUpperCase().replaceAll("-", "_")}_BASE_URL`;
175
+ return { ...base, baseUrl: process.env[urlVar] ?? base.baseUrl };
176
+ }
177
+
178
+ /**
179
+ * The same providers as a pi models.json, for tools that read that file but load no
180
+ * extension, such as trysquare. Built from the table alone: no server is asked, so a
181
+ * given version of this package always writes the same file. The key stays a `$VAR`
182
+ * reference that pi resolves from the environment.
183
+ */
184
+ export function modelsJson(endpoints: Endpoint[] = ENDPOINTS) {
185
+ const providers: Record<string, unknown> = {};
186
+ for (const base of endpoints) {
187
+ const endpoint = withBaseUrl(base);
188
+ const models = Object.entries(endpoint.models)
189
+ .filter(([, tuning]) => !tuning.hidden)
190
+ .map(([id, tuning]) => {
191
+ // Without a server to ask, the default would pass for a measured value.
192
+ if (tuning.contextWindow === undefined) {
193
+ throw new Error(`${endpoint.name}: ${id} has no contextWindow in the table`);
194
+ }
195
+ // The provider entry already says these three.
196
+ const { provider, api, baseUrl, ...model } = toModel(endpoint, { id });
197
+ return model;
198
+ });
199
+ providers[endpoint.id] = {
200
+ name: endpoint.name,
201
+ baseUrl: endpoint.baseUrl,
202
+ api: "openai-completions",
203
+ apiKey: `$${endpoint.envVar}`,
204
+ models,
205
+ };
206
+ }
207
+ return { providers };
208
+ }
209
+
172
210
  export default function (pi: ExtensionAPI) {
173
211
  for (const base of ENDPOINTS) {
174
- // <ID>_BASE_URL (ex: IA4DEV_ILAAS_BASE_URL) remplace l'URL par défaut, pour tester ou changer de serveur.
175
- const urlVar = `${base.id.toUpperCase().replaceAll("-", "_")}_BASE_URL`;
176
- const endpoint = { ...base, baseUrl: process.env[urlVar] ?? base.baseUrl };
212
+ const endpoint = withBaseUrl(base);
177
213
  pi.registerProvider(
178
214
  createProvider<"openai-completions">({
179
215
  id: endpoint.id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-for-dev/providers",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Pi providers for the AI4Dev course models (ILAAS and IA4Dev)",
5
5
  "keywords": [
6
6
  "pi",
@@ -30,7 +30,8 @@
30
30
  },
31
31
  "scripts": {
32
32
  "test": "node --test 'test/*.test.ts'",
33
- "typecheck": "tsc --noEmit"
33
+ "typecheck": "tsc --noEmit",
34
+ "export-models": "node scripts/export-models.ts"
34
35
  },
35
36
  "publishConfig": {
36
37
  "access": "public",
@@ -44,6 +45,7 @@
44
45
  "@earendil-works/pi-ai": "^1.3.0",
45
46
  "@earendil-works/pi-coding-agent": "^1.3.0",
46
47
  "@types/node": "^24.13.3",
48
+ "typebox": "^1.4.1",
47
49
  "typescript": "^5.9.0"
48
50
  }
49
51
  }