pi-incoai 0.1.0 → 0.1.2

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
@@ -57,8 +57,15 @@ trailing `:level` as a thinking level.
57
57
 
58
58
  ## Models
59
59
 
60
- The bundled baseline catalog is replaced by the live catalog on refresh. Pricing,
61
- context length, and reasoning support come from `GET /v1/models`.
60
+ The bundled baseline catalog makes the public models available immediately and
61
+ offline. Refreshing from `GET /v1/models` overrides the bundled metadata for the
62
+ same model ids and adds any models the API returns that are not bundled — newly
63
+ released public models and workspace/private models. Pricing, context length, and
64
+ reasoning support come from `GET /v1/models`.
65
+
66
+ Models that are not part of the bundled snapshot are listed first (new public
67
+ releases and workspace/private models), followed by the bundled public catalog;
68
+ each group is alphabetical.
62
69
 
63
70
  | Model | Context | Max output | Image input |
64
71
  |---|---|---|---|
@@ -91,6 +91,9 @@ const CATALOG_SNAPSHOT: IncoCatalogEntry[] = [
91
91
  { id: "minimax-m3:fast", name: "MiniMax M3 (Fast)", context_length: 1048576, pricing: { input: 0.6, cached_input: 0.12, output: 2.4 }, capabilities: { reasoning_effort: false }, modalities: { input: ["text", "image"] } },
92
92
  ];
93
93
 
94
+ /** Ids in the bundled snapshot; any other catalog id comes from the live API. */
95
+ const BUNDLED_MODEL_IDS = new Set(CATALOG_SNAPSHOT.map((entry) => entry.id));
96
+
94
97
  /** Build an error from a failed catalog response, preferring Inco's error envelope. */
95
98
  async function catalogError(response: Response): Promise<Error> {
96
99
  const status = `HTTP ${response.status} ${response.statusText}`.trim();
@@ -164,6 +167,32 @@ function toModel(entry: IncoCatalogEntry): Model<"openai-completions"> {
164
167
  };
165
168
  }
166
169
 
170
+ /** Sort models alphabetically by display name, falling back to id to break ties. */
171
+ function sortModels<T extends { name: string; id: string }>(models: readonly T[]): T[] {
172
+ return [...models].sort((a, b) => {
173
+ const byName = a.name.localeCompare(b.name, undefined, { sensitivity: "base", numeric: true });
174
+ return byName !== 0 ? byName : a.id.localeCompare(b.id, undefined, { sensitivity: "base", numeric: true });
175
+ });
176
+ }
177
+
178
+ /**
179
+ * Order the catalog with models that are not part of the bundled snapshot (new
180
+ * public releases, workspace/private models) first, then the bundled catalog,
181
+ * each group sorted alphabetically.
182
+ *
183
+ * `createProvider` merges the live overlay after the bundled baseline and
184
+ * appends ids that are absent from it, so without this new models would sit at
185
+ * the bottom of `/model`.
186
+ */
187
+ function orderModels<T extends { name: string; id: string }>(models: readonly T[]): T[] {
188
+ const live: T[] = [];
189
+ const bundled: T[] = [];
190
+ for (const model of models) {
191
+ (BUNDLED_MODEL_IDS.has(model.id) ? bundled : live).push(model);
192
+ }
193
+ return [...sortModels(live), ...sortModels(bundled)];
194
+ }
195
+
167
196
  /** Fetch the live catalog, authenticating when a key is available. */
168
197
  async function fetchIncoModels(context: RefreshModelsContext): Promise<Model<"openai-completions">[]> {
169
198
  const apiKey = context.credential?.type === "oauth" ? context.credential.access : context.credential?.key;
@@ -178,7 +207,7 @@ async function fetchIncoModels(context: RefreshModelsContext): Promise<Model<"op
178
207
  throw new Error("Inco model catalog returned no models");
179
208
  }
180
209
 
181
- return entries.filter((entry) => entry && typeof entry.id === "string").map(toModel);
210
+ return orderModels(entries.filter((entry) => entry && typeof entry.id === "string").map(toModel));
182
211
  }
183
212
 
184
213
  /**
@@ -211,15 +240,21 @@ function incoApiKeyAuth(): ApiKeyAuth {
211
240
  }
212
241
 
213
242
  export default function incoProviderExtension(pi: ExtensionAPI): void {
214
- pi.registerProvider(
215
- createProvider({
216
- id: PROVIDER_ID,
217
- name: PROVIDER_NAME,
218
- baseUrl: BASE_URL,
219
- auth: { apiKey: incoApiKeyAuth() },
220
- models: CATALOG_SNAPSHOT.map(toModel),
221
- api: openAICompletionsApi(),
222
- fetchModels: fetchIncoModels,
223
- }),
224
- );
243
+ const provider = createProvider({
244
+ id: PROVIDER_ID,
245
+ name: PROVIDER_NAME,
246
+ baseUrl: BASE_URL,
247
+ auth: { apiKey: incoApiKeyAuth() },
248
+ models: sortModels(CATALOG_SNAPSHOT.map(toModel)),
249
+ api: openAICompletionsApi(),
250
+ fetchModels: fetchIncoModels,
251
+ });
252
+
253
+ // Pi merges the live catalog after the bundled baseline, which would place new
254
+ // and workspace/private models last. Expose the ordered catalogs instead.
255
+ pi.registerProvider({
256
+ ...provider,
257
+ getModels: () => orderModels(provider.getModels()),
258
+ getAllModels: () => orderModels(provider.getModels()),
259
+ });
225
260
  }
package/package.json CHANGED
@@ -1,16 +1,22 @@
1
1
  {
2
2
  "name": "pi-incoai",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Inco AI (inco.ai) model provider extension for the Pi coding agent",
5
5
  "keywords": [
6
6
  "pi-package",
7
7
  "pi",
8
8
  "pi-coding-agent",
9
+ "extension",
9
10
  "inco",
10
11
  "incoai",
11
12
  "inference",
12
13
  "provider"
13
14
  ],
15
+ "pi": {
16
+ "extensions": [
17
+ "./extensions/pi-incoai/index.ts"
18
+ ]
19
+ },
14
20
  "license": "MIT",
15
21
  "author": "Sergiu Truta",
16
22
  "homepage": "https://github.com/serggiu/pi-incoai#readme",