Skip to content
3 changes: 3 additions & 0 deletions scripts/test-layout/layout-providers.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"free-directory-runcrate.test.ts": "providers"
}
2 changes: 1 addition & 1 deletion scripts/test-layout/layout.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"version": 1,
"root": "tests",
"explicitShards": ["layout-clients.json", "layout-codex-collision.json", "layout-agent-assertion.json", "layout-chat.json", "layout-replay.json", "layout-adapters.json", "layout-jev.json", "layout-workbuddy.json", "layout-combos.json", "layout-credit-renewal.json"],
"explicitShards": ["layout-clients.json", "layout-codex-collision.json", "layout-agent-assertion.json", "layout-chat.json", "layout-replay.json", "layout-adapters.json", "layout-jev.json", "layout-workbuddy.json", "layout-combos.json", "layout-credit-renewal.json", "layout-providers.json"],
"explicit": { "provider-input-modalities.test.ts": "providers", "combo-description.test.ts": "routing", "combo-description-catalog.test.ts": "codex-integration", "codex-provider-routing.test.ts": "codex-integration", "config-native-reasoning-retention.test.ts": "config", "server-native-reasoning-retention-merge.test.ts": "server", "server-native-reasoning-retention.test.ts": "server", "responses-retained-reasoning-stream-recovery.test.ts": "responses", "openai-native-hosted-search-history.test.ts": "adapters/openai", "responses-native-compact-search-history.test.ts": "responses", "config-hosted-search-projection.test.ts": "config", "gcp-credential-boundary.test.ts": "adapters/google", "gcp-sa-marker.test.ts": "adapters/google", "gcp-sa-adapter.test.ts": "adapters/google", "management-gcp-credential-input.test.ts": "server", "codex-preserve-signed-in.test.ts": "codex-integration", "cli-account-preservation.test.ts": "cli", "cli-provider-list-active.test.ts": "cli", "service-state-isolation.test.ts": "service", "compaction-history-framing.test.ts": "responses", "catalog-native-max-context.test.ts": "codex-integration", "protocol-argon-thought-boundary.test.ts": "responses", "service-log.test.ts": "service", "messages-native-empty-inline.test.ts": "claude-integration", "catalog-auto-review-override.test.ts": "codex-integration", "management-auto-review-settings.test.ts": "server", "management-auto-review-auth.test.ts": "server", "prefer-model-alias-ids.test.ts": "server", "responses-mimo-envelope.test.ts": "responses", "responses-call-id-linkage.test.ts": "responses", "bridge-terminal-refusal.test.ts": "adapters", "loop-terminal-refusal.test.ts": "images", "web-search-terminal-refusal.test.ts": "web-search", "combo-terminal-refusal.test.ts": "routing", "server-terminal-refusal-wiring.test.ts": "server", "incomplete-reason-normalization.test.ts": "responses", "advisor-retention.test.ts": "advisor", "responses-native-key-cooldown.test.ts": "responses", "responses-run-turn-key-readmission.test.ts": "responses", "qoder-spawn-readmission.test.ts": "adapters", "api-key-selection-cooldown.test.ts": "providers", "api-key-auto-pause-distribution.test.ts": "providers", "explicit-key-pause-oauth-boundary.test.ts": "providers", "server-chat-native-key-selection.test.ts": "server", "compact-key-selection.test.ts": "responses", "messages-native-key-selection.test.ts": "claude-integration", "coding-agent-windows-dispatch.test.ts": "adapters", "coding-agent-native-empty-argv.test.ts": "windows",
"service-proxy-env.test.ts": "service", "openai-chat-standalone-bare-block.test.ts": "adapters/openai",
"owner-registry-isolation.test.ts": "config",
Expand Down
12 changes: 10 additions & 2 deletions src/providers/free-directory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const FREE_PROVIDER_ACCESS_GROUPS = {
"cloudflare-ai", "cohere", "coze", "duckduckgo-web", "felo-web", "friendliai", "gemini", "github-models",
"groq", "hackclub", "huggingchat", "huggingface", "iflytek", "inference-net", "kiro", "liquid", "llm7",
"mistral", "morph", "muse-spark-web", "nara", "navy", "nlpcloud", "ollama-cloud", "opencode", "openrouter",
"ovhcloud", "pollinations", "puter", "qwen-web", "reka", "sambanova", "sparkdesk", "t3-web", "uncloseai",
"ovhcloud", "pollinations", "puter", "qwen-web", "reka", "runcrate", "sambanova", "sparkdesk", "t3-web", "uncloseai",
],
"recurring-uncapped": [
"agnes", "ainative", "aion", "baidu", "glm", "glm-cn", "kilo-gateway", "opencode-zen", "requesty",
Expand Down Expand Up @@ -119,6 +119,14 @@ const CONNECTABLE: Record<string, ConnectableOverride> = {
ovhcloud: openAi("https://oai.endpoints.kepler.ai.cloud.ovh.net/v1", "https://console.ovhcloud.com", { keyOptional: true, modelsUrl: "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1/models" }),
pollinations: openAi("https://gen.pollinations.ai/v1", "https://pollinations.ai", { keyOptional: true }),
reka: openAi("https://api.reka.ai/v1", "https://platform.reka.ai"),
// Checked 2026-10-27. Free terms: https://www.runcrate.ai/inference/free covers six named open
// models only, one allowance per GitHub account at least 30 days old, limited per session and per
// week and shared by the workspace; a workspace with credit continues as paid traffic once the
// allowance is used or free capacity is busy. Endpoint: https://www.runcrate.ai/docs/models/chat-completions.
// Operator: Aeonmind LLC (https://www.runcrate.ai/terms). GET /v1/models is public and returned
// 193 rows (text, image, video, audio, embeddings), all with paid list prices; six carry free_tier: true.
// Discovery is hybrid: the six free ids are seeded, and live discovery still lists the full paid catalog.
runcrate: openAi("https://api.runcrate.ai/v1", "https://www.runcrate.ai/dashboard/api-keys", { verification: "official", documentationUrl: "https://www.runcrate.ai/docs/models/chat-completions", modelsUrl: "https://api.runcrate.ai/v1/models", lastVerified: "2026-10-27", discovery: "hybrid", models: ["deepseek-ai/DeepSeek-V4.1-Flash", "deepseek-ai/DeepSeek-V4.1-Flash-1017", "zai-org/GLM-5.3-Flash", "zai-org/GLM-5.3", "openai/gpt-oss-120b", "Qwen/Qwen3.8-Flash"], note: "Only the six listed open models have a free allowance: one per GitHub account at least 30 days old, limited per session and per week, and shared by the workspace. Every other discovered model is paid; chat inference uses published per-token rates. When the allowance is used up or free capacity is busy, a workspace with credit continues as paid traffic; without credit, requests stop until the allowance resets or capacity frees up. Terms: https://www.runcrate.ai/inference/free" }),
sambanova: openAi("https://api.sambanova.ai/v1", "https://cloud.sambanova.ai/apis", { supportLevel: "supported", verification: "official", documentationUrl: "https://docs.sambanova.ai/docs/en/get-started/api-keys-urls", modelsUrl: "https://api.sambanova.ai/v1/models", lastVerified: "2026-08-02" }),
sparkdesk: openAi("https://spark-api-open.xf-yun.com/v1", "https://console.xfyun.cn", { verification: "official", documentationUrl: "https://www.xfyun.cn/doc/spark/HTTP%E8%B0%83%E7%94%A8%E6%96%87%E6%A1%A3.html" }),
agnes: openAi("https://apihub.agnes-ai.com/v1", "https://agnes-ai.com"),
Expand Down Expand Up @@ -202,7 +210,7 @@ const LABELS: Record<string, string> = {
ai21: "AI21", baichuan: "Baichuan", crusoe: "Crusoe", deepinfra: "DeepInfra", deepseek: "DeepSeek", doubao: "Doubao",
"freemodel-dev": "FreeModel.dev", sambanova: "SambaNova Cloud", nebius: "Nebius Token Factory",
novita: "Novita", nscale: "Nscale", nvidia: "NVIDIA NIM",
publicai: "PublicAI", qoder: "Qoder", "qoder-cn": "Qoder CN", sensenova: "SenseNova", stepfun: "StepFun", vertex: "Google Vertex AI",
publicai: "PublicAI", qoder: "Qoder", "qoder-cn": "Qoder CN", runcrate: "Runcrate", sensenova: "SenseNova", stepfun: "StepFun", vertex: "Google Vertex AI",
};

const referenceNote = "Reference entry only: no safe documented API integration is enabled. Configure it manually only with provider documentation; consumer-web cookies and anti-bot bypasses are intentionally unsupported.";
Expand Down
2 changes: 1 addition & 1 deletion tests/fixtures/test-layout-expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -1048,7 +1048,7 @@
"provider-outbound.test.ts": "providers", "provider-runtime-fetch.test.ts": "providers",
"provider-tls-profile.test.ts": "providers", "provider-payload.test.ts": "gui",
"provider-quota-label-sanitize.test.ts": "providers", "provider-quota-observed-marker.test.ts": "providers",
"provider-quota-abliteration.test.ts": "providers", "provider-quota-ollama-cloud.test.ts": "providers", "provider-quota.test.ts": "providers", "provider-registry-parity.test.ts": "providers", "haiku-5-5-catalog.test.ts": "providers",
"provider-quota-abliteration.test.ts": "providers", "provider-quota-ollama-cloud.test.ts": "providers", "provider-quota.test.ts": "providers", "provider-registry-parity.test.ts": "providers", "free-directory-runcrate.test.ts": "providers", "haiku-5-5-catalog.test.ts": "providers",
"provider-send-path-import.test.ts": "server", "provider-static-model-discovery.test.ts": "providers",
"provider-subscription-cli.test.ts": "gui", "provider-workspace-auth.test.ts": "gui",
"provider-workspace-data.test.ts": "gui", "provider-workspace-rail.test.ts": "gui",
Expand Down
46 changes: 46 additions & 0 deletions tests/providers/free-directory-runcrate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { describe, expect, test } from "bun:test";
import { FREE_PROVIDER_DIRECTORY } from "../../src/providers/free-directory";
import { PROVIDER_REGISTRY } from "../../src/providers/registry";

// Sibling of provider-registry-parity.test.ts, which sits at the file-size ratchet threshold.
describe("free-provider directory: Runcrate", () => {
test("the Runcrate row bounds its free allowance and keeps the paid catalog discoverable", () => {
// The row accepts a key and discovers the full /v1/models catalog, which is mostly paid. Only
// six named models have a free allowance (runcrate.ai/inference/free and free_tier in /v1/models,
// checked 2026-10-27), so the note must carry those bounds instead of advertising the whole catalog as free.
const row = FREE_PROVIDER_DIRECTORY.find(entry => entry.id === "runcrate");
expect(row).toMatchObject({
label: "Runcrate",
adapter: "openai-chat",
baseUrl: "https://api.runcrate.ai/v1",
authKind: "key",
accessGroups: ["recurring-or-keyless"],
supportLevel: "experimental",
verification: "official",
documentationUrl: "https://www.runcrate.ai/docs/models/chat-completions",
modelsUrl: "https://api.runcrate.ai/v1/models",
dashboardUrl: "https://www.runcrate.ai/dashboard/api-keys",
discovery: "hybrid",
liveModels: true,
lastVerified: "2026-10-27",
});
expect(row?.keyOptional).toBeUndefined();
expect(row?.models).toEqual([
"deepseek-ai/DeepSeek-V4.1-Flash",
"deepseek-ai/DeepSeek-V4.1-Flash-1017",
"zai-org/GLM-5.3-Flash",
"zai-org/GLM-5.3",
"openai/gpt-oss-120b",
"Qwen/Qwen3.8-Flash",
]);
const note = row?.note ?? "";
expect(note).toContain("Only the six listed open models have a free allowance");
expect(note).toContain("GitHub account at least 30 days old");
expect(note).toContain("per session and per week");
expect(note).toContain("Every other discovered model is paid");
expect(note).toContain("continues as paid traffic");
expect(note).toContain("https://www.runcrate.ai/inference/free");
// Directory-only: it must not become a canonical credential destination.
expect(PROVIDER_REGISTRY.some(entry => entry.id === "runcrate")).toBe(false);
});
});
Loading