Skip to content

feat(providers): add NaN.Builders API-key provider (chat, embeddings, audio, images, rerank, quota usage) - #16912

Merged
diegosouzapw merged 5 commits into
release/v3.8.52from
feat/port-pr-4429-nan-builders-provider
Oct 17, 2026
Merged

diegosouzapw merged 5 commits into
release/v3.8.52from
feat/port-pr-4429-nan-builders-provider

Conversation

@diegosouzapw

Copy link
Copy Markdown
Collaborator

Summary

  • Adds NaN.Builders (nan, https://api.nan.builders/v1) as an OpenAI-compatible API-key provider: chat with live /v1/models discovery, embeddings (qwen3-embedding, 4096 dims), STT (whisper), TTS (kokoro), image generation (flux-2-klein, qwen-image-2.1) and rerank (rerank).
  • Adds a usage fetcher for GET https://cloud-api.nan.builders/api/usage/quota that maps each per-model {model, tokensUsed, cap, periodEnd} row to a token quota in the limits panel. A malformed report (duplicate model, negative/non-integer counters, cap <= 0, unparseable periodEnd) is reported as unavailable; it is never partially guessed.
  • The OpenAI-compatible speech path now reads an optional defaultVoice from the audio provider config, so a Kokoro request without voice sends af_heart instead of the OpenAI stock alloy (which Kokoro does not have). Providers without defaultVoice still fall back to alloy.

Attribution

Thanks to @tasarren for the original implementation.

Changes

  • open-sse/config/providers/registry/nan/index.ts (new) + open-sse/config/providers/index.ts: chat registry entry (buildOpenAiCompatibleRegistryEntry, modelsUrl for live discovery, small seed list kept as the offline fallback).
  • src/shared/constants/providers/apikey/gateways.ts: display entry (name, color #6D5EF3, website, apiHint pointing to https://cloud.nan.builders). src/shared/constants/config.ts: PROVIDER_ENDPOINTS.nan.
  • open-sse/config/embeddingRegistry.ts, audioRegistry.ts (STT + TTS, new optional AudioProvider.defaultVoice), imageRegistryData.ts, rerankRegistry.ts: curated nan entries.
  • open-sse/handlers/audioSpeech.ts: voice: body.voice || providerConfig.defaultVoice || "alloy" (one line).
  • open-sse/services/usage/nan.ts (new) wired into usage.ts, usage/fetcherProviders.ts, usage/supportedProviders.ts, src/lib/usage/providerLimits.ts and the ProviderLimits label map. Errors go through sanitizeErrorMessage(), there is a 10 s timeout, and the upstream body is never echoed.
  • src/app/api/providers/[id]/models/discovery/providerSets.ts: nan added to the named OpenAI-style discovery set.
  • docs/reference/PROVIDER_REFERENCE.md: only the total (372 → 373), the API-key section count (249 → 250) and the new nan row.
  • Pinned-count tests: provider-node-reserved-prefix (431 → 432, id and alias are both nan) and providers-constants-split (249 → 250).

Not ported (deliberately):

  • Built-in /v1/images/edits for nan/flux-2-klein. classifyImageEditTarget in src/app/api/v1/images/edits/route.ts only accepts codex/fal/adobe/syntx/openrouter and custom nodes as built-in edit targets. Supporting NaN edits would change that public route's dispatch, so this PR leaves it out. Generation works. Edits can come in a follow-up.
  • The provider icon PNG. The dashboard falls back to textIcon ("NaN").

Provider count docs (expected red)

check:docs-counts (and therefore check:docs-all) is red on purpose. The aggregate "372 providers" prose in README/AGENTS.md/llm.txt/docs/SVGs/package.json description/skills now reads 373. Per the maintainer's decision for this campaign, the count prose and the protected agent-instruction surfaces are not touched here. A single count-sync PR lands at the end of the campaign. The real number also depends on merge order with the sibling provider PR #16889 (DaoXE), which bumps the same counters.

Test plan

  • node --import tsx/esm --test tests/unit/nan-provider.test.ts failed before the port (module open-sse/services/usage/nan.ts missing, registries returned null) and passes after: ℹ tests 11 ℹ pass 11 ℹ fail 0
  • Related suites (342 test files that reference the provider registries, usage provider lists, modality registries, discovery sets and pinned provider lists), run with the test:unit:ci preamble and an isolated DATA_DIR: ℹ tests 3248 ℹ pass 3244 ℹ fail 4. After updating the two pinned counts, only 2 failures are left:
    • check-docs-counts-sync.test.ts: the expected provider-count drift described above.
    • models-catalog-route.test.ts › "v1 models catalog exposes bare Codex-preferred IDs" (codex-auto-review must not be listed): pre-existing on base. It fails the same way on a pristine origin/release/v3.8.52 checkout (ℹ tests 44 ℹ pass 43 ℹ fail 1) and has nothing to do with nan.
  • Re-run of the pinned-count tests: provider-node-reserved-prefix + providers-constants-split pass.
  • npx vitest run tests/unit/ui/ProviderIcon-icon-url.test.tsx: Tests 94 passed (94)
  • node --import tsx scripts/check/check-provider-consistency.ts: OK — 290 entradas REGISTRY, 373 providers canônicos, 0 exceção(ões) registry-only
  • npm run typecheck:core: exit 0
  • npx eslint --suppressions-location config/quality/eslint-suppressions.json <changed files>: clean. npx prettier --check <changed files>: all formatted
  • node scripts/check/check-file-size.mjs: OK (no ceiling raised)
  • GITHUB_BASE_REF=release/v3.8.52 npm run check:test-masking: OK — 2 modificado(s) … sem enfraquecimento
  • check-pr-test-policy.mjs (PR context): Result: PASS
  • check:env-doc-sync, check:doc-links, check:fabricated-docs: pass. check:docs-counts: expected red (see above)

live_check (hold-vps)

The endpoints of this vendor have not been verified live. Before merge, on the VPS with a real NaN.Builders key:

# chat + discovery
curl -s $OMNI/v1/models -H "Authorization: Bearer $KEY" | jq '.data[].id' | grep '^"nan/'
curl -s $OMNI/v1/chat/completions -H "Authorization: Bearer $KEY" -H 'content-type: application/json' \
  -d '{"model":"nan/deepseek-v4-flash","messages":[{"role":"user","content":"ping"}]}' | jq .choices[0].message
# embeddings / rerank / tts / image
curl -s $OMNI/v1/embeddings -H "Authorization: Bearer $KEY" -H 'content-type: application/json' \
  -d '{"model":"nan/qwen3-embedding","input":"hi"}' | jq '.data[0].embedding | length'   # expect 4096
curl -s $OMNI/v1/rerank -H "Authorization: Bearer $KEY" -H 'content-type: application/json' \
  -d '{"model":"nan/rerank","query":"q","documents":["a","b"]}' | jq .results
curl -s $OMNI/v1/audio/speech -H "Authorization: Bearer $KEY" -H 'content-type: application/json' \
  -d '{"model":"nan/kokoro","input":"hello"}' -o /tmp/nan.mp3 && file /tmp/nan.mp3
curl -s $OMNI/v1/images/generations -H "Authorization: Bearer $KEY" -H 'content-type: application/json' \
  -d '{"model":"nan/flux-2-klein","prompt":"a red cube"}' | jq '.data | length'

Also confirm that the dashboard Limits panel shows per-model token bars for the NaN connection (quota endpoint cloud-api.nan.builders/api/usage/quota).

⚠️ base-red inherited: #15306 — unit/vitest/integration/package-artifact gates on release/v3.8.52

Register NaN.Builders (`nan`, https://api.nan.builders/v1) as an
OpenAI-compatible API-key provider: chat with live `/v1/models` discovery,
embeddings (qwen3-embedding, 4096 dims), STT (whisper), TTS (kokoro),
image generation (flux-2-klein, qwen-image-2.1) and rerank.

Add a per-model token quota fetcher (GET
https://cloud-api.nan.builders/api/usage/quota) that maps each
{model, tokensUsed, cap, periodEnd} row to a token quota and rejects
malformed reports instead of guessing.

The OpenAI-compatible speech path now honors an optional
`defaultVoice` on the audio provider config, so a Kokoro request
without `voice` sends `af_heart` instead of the OpenAI stock `alloy`.

Co-authored-by: Tasa <93864300+tasarren@users.noreply.github.com>
Inspired-by: decolua/9router#4429
@diegosouzapw diegosouzapw added the hold-vps PR verde, merge aguardando validação live (release-drain) label Oct 16, 2026
Comment thread tests/unit/nan-provider.test.ts Outdated
for (const kind of ["llm", "embedding", "stt", "tts", "image"]) {
assert.ok(entry.serviceKinds.includes(kind), `serviceKinds must include ${kind}`);
}
assert.ok(String(entry.apiHint).includes("https://cloud.nan.builders"));
…vider count to 373

Replace the substring URL check flagged by CodeQL (incomplete URL substring
sanitization) with an exact-string assertion, and update the public provider count
the docs-counts gate requires now that nan is registered (llm.txt mirrors included).
@diegosouzapw
diegosouzapw merged commit 7ff5f6e into release/v3.8.52 Oct 17, 2026
7 of 9 checks passed
diegosouzapw added a commit that referenced this pull request Oct 17, 2026
NaN.Builders (#16912) landed first and took the count to 373; DaoXE makes it 374.
Regenerate PROVIDER_REFERENCE.md and update the count wherever the docs-counts gate
requires it (llm.txt mirrors included).
diegosouzapw added a commit that referenced this pull request Oct 17, 2026
Merged via /merge-batch (2026-10-16 wave). Fixes applied on top of the port: the CodeQL incomplete-URL-substring alert in tests/unit/provider-daoxe-registry.test.ts now asserts the apiHint exactly; reconciled with #16912 (NaN.Builders, landed first) so both providers are registered and the count pins move once more; the public provider count goes 373 -> 374 (README, AGENTS.md, llm.txt + mirrors, package.json, generated skills — owner-approved, count-only lines). Focused tests 42/42 (DaoXE + NaN + reserved-prefix + constants-split); check:provider-consistency OK (374 canonical); check:docs-counts no strict drift (2 soft executor-count drifts are inherited from the tip); agent-skills-sync Generated 0; docs-sync PASS; boundary 774/774; eslint clean. Remaining CI reds are the inherited base-red #15306.
@github-actions

Copy link
Copy Markdown
Contributor

CI Coverage Report

  • Coverage job: skipped
  • PR test policy: success

Coverage artifact was not available for this run.

@diegosouzapw diegosouzapw mentioned this pull request Oct 18, 2026
@diegosouzapw
diegosouzapw deleted the feat/port-pr-4429-nan-builders-provider branch October 18, 2026 23:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

hold-vps PR verde, merge aguardando validação live (release-drain)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants