diff --git a/CHANGELOG.md b/CHANGELOG.md index 27ab3c617e..399c68cc50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ _In development — bullets added per PR; finalized at release._ ### 🔧 Bug Fixes +- **feat(providers):** add Pioneer AI (Fastino Labs) provider — OpenAI-compatible chat completions at `api.pioneer.ai/v1`. Registered with alias `pn`, `X-API-Key` auth, and a catalog of 10 open-tier serverless models (Qwen3, Llama 3.1/3.2, Gemma 3, SmolLM3). Free $75 credits, no credit card required. Gated enterprise models (Claude/GPT/Gemini) require prior fine-tuning on the Pioneer platform and are intentionally excluded from the catalog. (thanks @HikiNarou) + - **feat(sse): auto-promote successful combo model**: a new opt-in `comboAutoPromoteEnabled` setting reorders a combo's persisted model list so that, when a combo model responds successfully, it is moved to position #1 for future requests. (thanks @arssnndr) - **fix(sse):** dense, deterministic `response.output` ordering in `response.completed` — items are now sorted by their actual `output_index` (via a recorded-as-emitted accumulator + stable sort) instead of being rebuilt from unordered state dicts; `normalizeOutputIndex` replaces fragile `parseInt` calls for robust index coercion; superseded tool calls (replaced at the same index mid-stream) are excluded from the final output array. (thanks @Marco9113) diff --git a/open-sse/config/providers/index.ts b/open-sse/config/providers/index.ts index ef02d0697f..988e419a79 100644 --- a/open-sse/config/providers/index.ts +++ b/open-sse/config/providers/index.ts @@ -167,6 +167,7 @@ import { openadapterProvider } from "./registry/openadapter/index.ts"; import { ditProvider } from "./registry/dit/index.ts"; import { tokenrouterProvider } from "./registry/tokenrouter/index.ts"; import { codebuddy_cnProvider } from "./registry/codebuddy-cn/index.ts"; +import { pioneerProvider } from "./registry/pioneer/index.ts"; export const REGISTRY: Record = { aimlapi: aimlapiProvider, @@ -336,4 +337,5 @@ export const REGISTRY: Record = { dit: ditProvider, tokenrouter: tokenrouterProvider, "codebuddy-cn": codebuddy_cnProvider, + pioneer: pioneerProvider, }; diff --git a/open-sse/config/providers/registry/pioneer/index.ts b/open-sse/config/providers/registry/pioneer/index.ts new file mode 100644 index 0000000000..c577ff3ff5 --- /dev/null +++ b/open-sse/config/providers/registry/pioneer/index.ts @@ -0,0 +1,41 @@ +import type { RegistryEntry } from "../../shared.ts"; + +/** + * Pioneer AI by Fastino Labs — OpenAI-compatible chat completions. + * + * Endpoint: https://api.pioneer.ai/v1/chat/completions + * Auth: X-API-Key header with a pio_sk_... key (Bearer also accepted upstream). + * + * Only models with supports_on_demand_inference=true work directly with a bare + * pio_sk_ key. Gated/enterprise models (Claude/GPT/Gemini etc.) require a prior + * fine-tuning job and are called via the resulting job id, not the base model id. + * + * Source of truth for models: GET https://api.pioneer.ai/base-models + * ?supports_inference=true&task_type=decoder + * filter: supports_on_demand_inference=true + */ +export const pioneerProvider: RegistryEntry = { + id: "pioneer", + alias: "pn", + format: "openai", + executor: "default", + baseUrl: "https://api.pioneer.ai/v1/chat/completions", + authType: "apikey", + // Pioneer standardises on X-API-Key (preferred over Bearer). + // The default executor resolves "x-api-key" to the X-API-Key header. + authHeader: "x-api-key", + models: [ + // === Open-tier serverless inference === + // These models support on-demand inference with any pio_sk_ key. + { id: "Qwen/Qwen3-32B", name: "Qwen3 32B" }, + { id: "Qwen/Qwen3.6-27B", name: "Qwen3.6 27B" }, + { id: "Qwen/Qwen3.5-9B", name: "Qwen3.5 9B" }, + { id: "Qwen/Qwen3-8B", name: "Qwen3 8B" }, + { id: "Qwen/Qwen3-4B-Base", name: "Qwen3 4B Base" }, + { id: "Qwen/Qwen3-1.7B-Base", name: "Qwen3 1.7B Base" }, + { id: "meta-llama/Llama-3.1-8B-Instruct", name: "Llama 3.1 8B Instruct" }, + { id: "meta-llama/Llama-3.2-1B-Instruct", name: "Llama 3.2 1B Instruct" }, + { id: "google/gemma-3-4b-pt", name: "Gemma 3 4B (Pretrained)" }, + { id: "HuggingFaceTB/SmolLM3-3B-Base", name: "SmolLM3 3B Base" }, + ], +}; diff --git a/src/shared/constants/providers/apikey/frontier-labs.ts b/src/shared/constants/providers/apikey/frontier-labs.ts index 1339e374e1..dc99ff4be1 100644 --- a/src/shared/constants/providers/apikey/frontier-labs.ts +++ b/src/shared/constants/providers/apikey/frontier-labs.ts @@ -27,6 +27,23 @@ export const APIKEY_PROVIDERS_FRONTIER = { hasFree: true, freeNote: "$10/month recurring free API credits", }, + pioneer: { + id: "pioneer", + alias: "pn", + name: "Pioneer AI", + icon: "rocket_launch", + color: "#7C5CFF", + textIcon: "PN", + website: "https://pioneer.ai", + notice: { + text: "Pioneer AI by Fastino Labs. Free $75 usage credits, no credit card required. Use API key auth with a pio_sk_... key. Only open-tier models (Qwen3, Llama, Gemma, SmolLM) work directly — gated models (Claude/GPT/Gemini) require prior fine-tuning via the Pioneer platform.", + apiKeyUrl: "https://agent.pioneer.ai/settings/api-keys", + signupUrl: "https://agent.pioneer.ai/auth", + }, + hasFree: true, + freeNote: "$75 free usage credits — no credit card required", + serviceKinds: ["llm"], + }, anthropic: { id: "anthropic", alias: "anthropic", diff --git a/tests/unit/pioneer-provider.test.ts b/tests/unit/pioneer-provider.test.ts new file mode 100644 index 0000000000..137859c3e8 --- /dev/null +++ b/tests/unit/pioneer-provider.test.ts @@ -0,0 +1,83 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { APIKEY_PROVIDERS, AI_PROVIDERS } from "../../src/shared/constants/providers.ts"; +import { REGISTRY } from "../../open-sse/config/providerRegistry.ts"; +import { PROVIDERS as LEGACY_PROVIDERS } from "../../open-sse/config/constants.ts"; + +test("pioneer is registered as an API-key provider in the UI catalog", () => { + const pioneer = APIKEY_PROVIDERS.pioneer; + assert.ok(pioneer, "APIKEY_PROVIDERS.pioneer must exist"); + assert.equal(pioneer.id, "pioneer"); + assert.equal(pioneer.alias, "pn"); + assert.equal(pioneer.name, "Pioneer AI"); + assert.equal(pioneer.color, "#7C5CFF"); +}); + +test("pioneer appears in AI_PROVIDERS (composed view)", () => { + const pioneer = AI_PROVIDERS.pioneer; + assert.ok(pioneer, "AI_PROVIDERS.pioneer must exist"); + assert.equal(pioneer.id, "pioneer"); +}); + +test("pioneer registry entry is correct (format, auth, executor)", () => { + const pioneer = REGISTRY.pioneer; + assert.ok(pioneer, "REGISTRY.pioneer must exist"); + assert.equal(pioneer.format, "openai"); + assert.equal(pioneer.executor, "default"); + assert.equal(pioneer.authType, "apikey"); + // X-API-Key auth (not Bearer) — matches upstream preference + assert.equal(pioneer.authHeader, "x-api-key"); + assert.equal(pioneer.baseUrl, "https://api.pioneer.ai/v1/chat/completions"); + assert.equal(pioneer.alias, "pn"); +}); + +test("pioneer has open-tier models with supports_on_demand_inference", () => { + const pioneer = REGISTRY.pioneer; + const ids = pioneer.models.map((m) => m.id); + // Qwen3 models + assert.ok(ids.includes("Qwen/Qwen3-32B"), "must include Qwen3 32B"); + assert.ok(ids.includes("Qwen/Qwen3-8B"), "must include Qwen3 8B"); + // Llama models + assert.ok(ids.includes("meta-llama/Llama-3.1-8B-Instruct"), "must include Llama 3.1 8B Instruct"); + assert.ok(ids.includes("meta-llama/Llama-3.2-1B-Instruct"), "must include Llama 3.2 1B Instruct"); + // At least 10 models total (open-tier catalog) + assert.ok(pioneer.models.length >= 10, `expected >= 10 models, got ${pioneer.models.length}`); + // Gated models (Claude/GPT/Gemini) must NOT appear — they require fine-tuning first + assert.ok( + !ids.some((id) => id.toLowerCase().includes("claude")), + "gated Claude models must not be in catalog" + ); + assert.ok( + !ids.some((id) => id.toLowerCase().includes("gpt")), + "gated GPT models must not be in catalog" + ); + assert.ok( + !ids.some((id) => id.toLowerCase().includes("gemini")), + "gated Gemini models must not be in catalog" + ); +}); + +test("pioneer legacy PROVIDERS entry resolves from generated map", () => { + const legacy = LEGACY_PROVIDERS.pioneer; + assert.ok(legacy, "LEGACY_PROVIDERS.pioneer must exist (generated from REGISTRY)"); + assert.equal(legacy.format, "openai"); + // Should not have OAuth fields — pioneer is API-key only + assert.equal(legacy.clientId, undefined); + assert.equal(legacy.clientSecret, undefined); + assert.equal(legacy.tokenUrl, undefined); +}); + +test("pioneer has hasFree flag and free signup notice", () => { + const pioneer = APIKEY_PROVIDERS.pioneer; + assert.equal(pioneer.hasFree, true, "pioneer should advertise free tier"); + assert.ok(pioneer.freeNote?.includes("$75"), "freeNote should mention $75 credits"); + assert.ok( + pioneer.notice?.signupUrl?.includes("pioneer.ai"), + "signupUrl should point to pioneer.ai" + ); + assert.ok( + pioneer.notice?.apiKeyUrl?.includes("pioneer.ai"), + "apiKeyUrl should point to pioneer.ai" + ); +});