From d5dd3905f27ce73c4542d1b15d5ec0f97bbbafd5 Mon Sep 17 00:00:00 2001 From: olddonkey Date: Thu, 30 Jul 2026 05:05:45 -0700 Subject: [PATCH 1/2] feat(providers): add Hyperbolic preset --- .../docs/getting-started/quickstart.md | 2 +- .../src/content/docs/guides/providers.md | 8 +- .../docs/ja/getting-started/quickstart.md | 2 +- .../src/content/docs/ja/guides/providers.md | 8 +- .../docs/ko/getting-started/quickstart.md | 2 +- .../src/content/docs/ko/guides/providers.md | 8 +- .../docs/ru/getting-started/quickstart.md | 2 +- .../src/content/docs/ru/guides/providers.md | 8 +- .../docs/zh-cn/getting-started/quickstart.md | 2 +- .../content/docs/zh-cn/guides/providers.md | 7 +- src/providers/registry.ts | 16 ++ tests/fixtures/hyperbolic-models.json | 13 ++ tests/hyperbolic-provider.test.ts | 189 ++++++++++++++++++ tests/provider-registry-parity.test.ts | 2 +- 14 files changed, 258 insertions(+), 11 deletions(-) create mode 100644 tests/fixtures/hyperbolic-models.json create mode 100644 tests/hyperbolic-provider.test.ts diff --git a/docs-site/src/content/docs/getting-started/quickstart.md b/docs-site/src/content/docs/getting-started/quickstart.md index ed8d79858..1e8d9ec84 100644 --- a/docs-site/src/content/docs/getting-started/quickstart.md +++ b/docs-site/src/content/docs/getting-started/quickstart.md @@ -13,7 +13,7 @@ ocx init `ocx init` walks you through: -1. **Pick a provider** — choose one of the 50 built-in registry presets or `custom` to type a base +1. **Pick a provider** — choose one of the 61 built-in registry presets or `custom` to type a base URL and adapter. 2. **API key** — paste a key, or reference an environment variable like `${ANTHROPIC_API_KEY}`. 3. **Default model** — for key, local, and custom providers, accept the preset or enter a model id. diff --git a/docs-site/src/content/docs/guides/providers.md b/docs-site/src/content/docs/guides/providers.md index e14f0d6e0..abe793fab 100644 --- a/docs-site/src/content/docs/guides/providers.md +++ b/docs-site/src/content/docs/guides/providers.md @@ -185,7 +185,7 @@ selectors, then retry. Signing in from a machine with no existing `kiro-cli` ses ## 3. API-key catalog -opencodex ships 53 built-in presets: 42 key-based, seven OAuth, three local, and the default +opencodex ships 61 built-in presets: 50 key-based, seven OAuth, three local, and the default ChatGPT-forward preset. The dashboard's **Add provider** picker opens a key provider's dashboard, validates the key, and stores it. Notable entries: @@ -202,6 +202,7 @@ validates the key, and stores it. Notable entries: | MiniMax · MiniMax (CN) | `https://api.minimax.io/v1` · `https://api.minimaxi.com/v1` | | DeepSeek | `https://api.deepseek.com` | | Cerebras | `https://api.cerebras.ai/v1` | +| Hyperbolic | `https://api.hyperbolic.xyz/v1` | | Together | `https://api.together.xyz/v1` | | Fireworks | `https://api.fireworks.ai/inference/v1` | | Moonshot (Kimi API) · Kimi (coding) | `https://api.moonshot.ai/v1` · `https://api.kimi.com/coding/v1` | @@ -221,6 +222,11 @@ validates the key, and stores it. Notable entries: Most use the `openai-chat` adapter with a bearer key; a few that expose only an Anthropic-compatible endpoint (e.g. **Xiaomi MiMo**) use the `anthropic` adapter (`x-api-key`). +**Hyperbolic discovery.** The preset reads `/v1/models` with the configured bearer key, preserves +slash-containing native model ids, and caps live discovery at 256 KiB and 256 raw rows. It covers +serverless text and vision-language chat only; Hyperbolic's separate image, audio, and GPU endpoints +are out of scope. Create keys at [Hyperbolic](https://app.hyperbolic.ai). + > **Tencent Cloud Coding Plan usage restriction:** Tencent documents this subscription for > interactive coding tools only. General API automation, custom application backends, and > non-interactive batch use are prohibited and may cause the plan key to be suspended. diff --git a/docs-site/src/content/docs/ja/getting-started/quickstart.md b/docs-site/src/content/docs/ja/getting-started/quickstart.md index dbf80359e..ce659483d 100644 --- a/docs-site/src/content/docs/ja/getting-started/quickstart.md +++ b/docs-site/src/content/docs/ja/getting-started/quickstart.md @@ -13,7 +13,7 @@ ocx init `ocx init` は次の手順を案内します: -1. **プロバイダー選択** — 組み込みレジストリのプリセット 50 個から一つを選ぶか、`custom` を選んで +1. **プロバイダー選択** — 組み込みレジストリのプリセット 61 個から一つを選ぶか、`custom` を選んで base URL とアダプターを直接入力します。 2. **API キー** — キーを貼り付けるか、`${ANTHROPIC_API_KEY}` のような環境変数を参照します。 3. **デフォルトモデル** — API キー、ローカル、custom プロバイダーではプリセット値を使うかモデル ID を直接入力します。 diff --git a/docs-site/src/content/docs/ja/guides/providers.md b/docs-site/src/content/docs/ja/guides/providers.md index 67548d50e..97098fd4e 100644 --- a/docs-site/src/content/docs/ja/guides/providers.md +++ b/docs-site/src/content/docs/ja/guides/providers.md @@ -116,7 +116,7 @@ Kiro のログインには Kiro CLI が必要です。`curl -fsSL https://cli.ki ## 3. API キーカタログ -opencodex v2.7.1 には組み込みプリセットが 50 個含まれています。キー方式 40、OAuth 6、ローカル 3、 +opencodex には組み込みプリセットが 61 個含まれています。キー方式 50、OAuth 7、ローカル 3、 デフォルト ChatGPT 転送プリセット 1 です。ダッシュボードの **Add provider** ピッカーはキー発行ページを開き、 入力したキーを検証した後保存します。主な項目は以下のとおりです: @@ -133,6 +133,7 @@ opencodex v2.7.1 には組み込みプリセットが 50 個含まれていま | MiniMax · MiniMax (CN) | `https://api.minimax.io/v1` · `https://api.minimaxi.com/v1` | | DeepSeek | `https://api.deepseek.com` | | Cerebras | `https://api.cerebras.ai/v1` | +| Hyperbolic | `https://api.hyperbolic.xyz/v1` | | Together | `https://api.together.xyz/v1` | | Fireworks | `https://api.fireworks.ai/inference/v1` | | Moonshot (Kimi API) · Kimi (coding) | `https://api.moonshot.ai/v1` · `https://api.kimi.com/coding/v1` | @@ -152,6 +153,11 @@ opencodex v2.7.1 には組み込みプリセットが 50 個含まれていま 大半は bearer キーと共に `openai-chat` アダプターを使い、Anthropic 互換エンドポイントのみを公開する一部 (例: **Xiaomi MiMo**)は `anthropic` アダプター(`x-api-key`)を使います。 +**Hyperbolic の discovery:** preset は設定済みの bearer キーで `/v1/models` を読み、スラッシュを含む +ネイティブモデル ID を保持し、live discovery を 256 KiB と raw 256 行に制限します。serverless text / +vision-language chat のみを対象とし、別系統の image、audio、GPU endpoint は対象外です。キーは +[Hyperbolic](https://app.hyperbolic.ai) で作成します。 + > **Tencent Cloud Coding Plan の利用制限:** Tencent はこのサブスクリプションを対話型 > コーディングツール専用としています。一般的な API 自動化、カスタムアプリのバックエンド、 > 非対話型バッチ利用は禁止されており、プランキーが停止される場合があります。 diff --git a/docs-site/src/content/docs/ko/getting-started/quickstart.md b/docs-site/src/content/docs/ko/getting-started/quickstart.md index d478de1a2..df6c39f0e 100644 --- a/docs-site/src/content/docs/ko/getting-started/quickstart.md +++ b/docs-site/src/content/docs/ko/getting-started/quickstart.md @@ -13,7 +13,7 @@ ocx init `ocx init`은 다음 과정을 안내합니다: -1. **프로바이더 선택** — 내장 레지스트리 프리셋 50개 중 하나를 고르거나, `custom`을 선택해 +1. **프로바이더 선택** — 내장 레지스트리 프리셋 61개 중 하나를 고르거나, `custom`을 선택해 base URL과 adapter를 직접 입력합니다. 2. **API 키** — 키를 붙여넣거나, `${ANTHROPIC_API_KEY}`와 같은 환경 변수를 참조합니다. 3. **기본 모델** — API 키, 로컬, custom 프로바이더에서는 프리셋 값을 쓰거나 모델 id를 직접 입력합니다. diff --git a/docs-site/src/content/docs/ko/guides/providers.md b/docs-site/src/content/docs/ko/guides/providers.md index 8cdae100a..0a7806161 100644 --- a/docs-site/src/content/docs/ko/guides/providers.md +++ b/docs-site/src/content/docs/ko/guides/providers.md @@ -116,7 +116,7 @@ Kiro 로그인에는 Kiro CLI가 필요합니다. `curl -fsSL https://cli.kiro.d ## 3. API 키 카탈로그 -opencodex v2.7.1에는 빌트인 프리셋이 50개 들어 있습니다. 키 방식 40개, OAuth 6개, 로컬 3개, +opencodex에는 빌트인 프리셋이 61개 들어 있습니다. 키 방식 50개, OAuth 7개, 로컬 3개, 기본 ChatGPT 포워드 프리셋 1개입니다. 대시보드의 **Add provider** 선택기는 키 발급 페이지를 열고, 입력한 키를 검증한 뒤 저장합니다. 주요 항목은 다음과 같습니다: @@ -133,6 +133,7 @@ opencodex v2.7.1에는 빌트인 프리셋이 50개 들어 있습니다. 키 방 | MiniMax · MiniMax (CN) | `https://api.minimax.io/v1` · `https://api.minimaxi.com/v1` | | DeepSeek | `https://api.deepseek.com` | | Cerebras | `https://api.cerebras.ai/v1` | +| Hyperbolic | `https://api.hyperbolic.xyz/v1` | | Together | `https://api.together.xyz/v1` | | Fireworks | `https://api.fireworks.ai/inference/v1` | | Moonshot (Kimi API) · Kimi (coding) | `https://api.moonshot.ai/v1` · `https://api.kimi.com/coding/v1` | @@ -152,6 +153,11 @@ opencodex v2.7.1에는 빌트인 프리셋이 50개 들어 있습니다. 키 방 대부분은 bearer 키와 함께 `openai-chat` 어댑터를 사용하며, Anthropic 호환 엔드포인트만 노출하는 일부 (예: **Xiaomi MiMo**)는 `anthropic` 어댑터(`x-api-key`)를 사용합니다. +**Hyperbolic 검색:** 프리셋은 설정된 bearer 키로 `/v1/models`를 읽고, 슬래시가 포함된 네이티브 모델 ID를 +보존하며 live discovery를 256 KiB와 raw 행 256개로 제한합니다. serverless text 및 vision-language chat만 +대상으로 하며 별도 image, audio, GPU 엔드포인트는 범위에서 제외합니다. 키는 +[Hyperbolic](https://app.hyperbolic.ai)에서 생성합니다. + > **Tencent Cloud Coding Plan 사용 제한:** Tencent는 이 구독을 대화형 코딩 도구 전용으로 > 안내합니다. 일반 API 자동화, 사용자 애플리케이션 백엔드 및 비대화형 일괄 호출은 금지되며 > 플랜 키가 정지될 수 있습니다. diff --git a/docs-site/src/content/docs/ru/getting-started/quickstart.md b/docs-site/src/content/docs/ru/getting-started/quickstart.md index 74023c3a0..de1187710 100644 --- a/docs-site/src/content/docs/ru/getting-started/quickstart.md +++ b/docs-site/src/content/docs/ru/getting-started/quickstart.md @@ -13,7 +13,7 @@ ocx init `ocx init` проведёт вас по следующим шагам: -1. **Выбор провайдера** — выберите один из 50 встроенных пресетов реестра или `custom`, чтобы +1. **Выбор провайдера** — выберите один из 61 встроенного пресета реестра или `custom`, чтобы ввести базовый URL и адаптер вручную. 2. **API-ключ** — вставьте ключ или сошлитесь на переменную окружения вида `${ANTHROPIC_API_KEY}`. 3. **Модель по умолчанию** — для провайдеров с ключом, локальных и `custom` примите значение из diff --git a/docs-site/src/content/docs/ru/guides/providers.md b/docs-site/src/content/docs/ru/guides/providers.md index f23e818d8..5710b1425 100644 --- a/docs-site/src/content/docs/ru/guides/providers.md +++ b/docs-site/src/content/docs/ru/guides/providers.md @@ -126,7 +126,7 @@ OAuth-провайдеры, чьи учётные данные содержат ## 3. Каталог API-ключей -opencodex поставляется с 53 встроенными пресетами: 42 на основе ключей, семь OAuth, три локальных и +opencodex поставляется с 61 встроенным пресетом: 50 на основе ключей, семь OAuth, три локальных и пресет ChatGPT-форварда по умолчанию. Селектор **Add provider** в дашборде открывает страницу выдачи ключей провайдера, проверяет ключ и сохраняет его. Наиболее заметные записи: @@ -143,6 +143,7 @@ opencodex поставляется с 53 встроенными пресетам | MiniMax · MiniMax (CN) | `https://api.minimax.io/v1` · `https://api.minimaxi.com/v1` | | DeepSeek | `https://api.deepseek.com` | | Cerebras | `https://api.cerebras.ai/v1` | +| Hyperbolic | `https://api.hyperbolic.xyz/v1` | | Together | `https://api.together.xyz/v1` | | Fireworks | `https://api.fireworks.ai/inference/v1` | | Moonshot (Kimi API) · Kimi (coding) | `https://api.moonshot.ai/v1` · `https://api.kimi.com/coding/v1` | @@ -163,6 +164,11 @@ opencodex поставляется с 53 встроенными пресетам только Anthropic-совместимую конечную точку (например, **Xiaomi MiMo**), используют адаптер `anthropic` (`x-api-key`). +**Discovery для Hyperbolic.** Пресет читает `/v1/models` с настроенным bearer-ключом, сохраняет +нативные id моделей со знаком `/` и ограничивает live discovery размером 256 KiB и 256 исходными +строками. Он охватывает только serverless text и vision-language chat; отдельные image, audio и GPU +endpoint в него не входят. Ключи создаются в [Hyperbolic](https://app.hyperbolic.ai). + > **Ограничение Tencent Cloud Coding Plan:** Tencent разрешает использовать эту подписку только > в интерактивных инструментах программирования. Автоматизация общего API, серверы пользовательских > приложений и неинтерактивные пакетные вызовы запрещены и могут привести к блокировке ключа плана. diff --git a/docs-site/src/content/docs/zh-cn/getting-started/quickstart.md b/docs-site/src/content/docs/zh-cn/getting-started/quickstart.md index 4c2f69e49..be5f979d5 100644 --- a/docs-site/src/content/docs/zh-cn/getting-started/quickstart.md +++ b/docs-site/src/content/docs/zh-cn/getting-started/quickstart.md @@ -13,7 +13,7 @@ ocx init `ocx init` 会引导你完成: -1. **选择 provider** —— 从内置 registry 的 50 个预设中选择一个,或选择 `custom` 手动输入 +1. **选择 provider** —— 从内置 registry 的 61 个预设中选择一个,或选择 `custom` 手动输入 base URL 和 adapter。 2. **API key** —— 粘贴一个 key,或引用一个环境变量,例如 `${ANTHROPIC_API_KEY}`。 3. **默认模型** —— 对于 API key、本地和 custom provider,可接受预设值或输入模型 id。 diff --git a/docs-site/src/content/docs/zh-cn/guides/providers.md b/docs-site/src/content/docs/zh-cn/guides/providers.md index b749d3837..53a046d06 100644 --- a/docs-site/src/content/docs/zh-cn/guides/providers.md +++ b/docs-site/src/content/docs/zh-cn/guides/providers.md @@ -108,7 +108,7 @@ Kiro 登录需要 Kiro CLI:使用 `curl -fsSL https://cli.kiro.dev/install | b ## 3. API 密钥目录 -opencodex v2.7.1 内置 50 个预设:40 个密钥预设、6 个 OAuth 预设、3 个本地预设,以及默认的 +opencodex 内置 61 个预设:50 个密钥预设、7 个 OAuth 预设、3 个本地预设,以及默认的 ChatGPT 转发预设。仪表盘的 **Add provider** 选择器会打开密钥提供商的控制台,验证并保存密钥。 主要条目包括: @@ -125,6 +125,7 @@ ChatGPT 转发预设。仪表盘的 **Add provider** 选择器会打开密钥提 | MiniMax · MiniMax (CN) | `https://api.minimax.io/v1` · `https://api.minimaxi.com/v1` | | DeepSeek | `https://api.deepseek.com` | | Cerebras | `https://api.cerebras.ai/v1` | +| Hyperbolic | `https://api.hyperbolic.xyz/v1` | | Together | `https://api.together.xyz/v1` | | Fireworks | `https://api.fireworks.ai/inference/v1` | | Moonshot (Kimi API) · Kimi (coding) | `https://api.moonshot.ai/v1` · `https://api.kimi.com/coding/v1` | @@ -143,6 +144,10 @@ ChatGPT 转发预设。仪表盘的 **Add provider** 选择器会打开密钥提 大多数使用带 bearer 密钥的 `openai-chat` adapter;少数仅暴露 Anthropic 兼容端点的提供商(例如 **Xiaomi MiMo**)使用 `anthropic` adapter(`x-api-key`)。 +**Hyperbolic 发现:**该预设会使用已配置的 bearer 密钥读取 `/v1/models`,保留含 `/` 的原生模型 id, +并将实时发现限制为 256 KiB 和 256 条原始记录。它仅覆盖 serverless text 与 vision-language chat;独立的 +image、audio 和 GPU 端点不在范围内。密钥可在 [Hyperbolic](https://app.hyperbolic.ai)创建。 + > **腾讯云 Coding Plan 使用限制:**腾讯将此订阅限定为交互式编程工具使用。禁止通用 API > 自动化、自定义应用后端和非交互式批量调用;违规使用可能导致套餐密钥被停用。 diff --git a/src/providers/registry.ts b/src/providers/registry.ts index 663e8c8ec..5e397a6e5 100644 --- a/src/providers/registry.ts +++ b/src/providers/registry.ts @@ -828,6 +828,22 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [ }, // llama-3.3-70b was deprecated by Cerebras on 2026-02-16. Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md. { id: "cerebras", label: "Cerebras", baseUrl: "https://api.cerebras.ai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://cloud.cerebras.ai/platform/apikeys", defaultModel: "gpt-oss-120b" }, + { + id: "hyperbolic", + label: "Hyperbolic", + baseUrl: "https://api.hyperbolic.xyz/v1", + adapter: "openai-chat", + authKind: "key", + dashboardUrl: "https://app.hyperbolic.ai", + liveModels: true, + preserveCustomDestination: true, + modelDiscovery: { + path: "models", + maxResponseBytes: 256 * 1024, + maxModels: 256, + }, + note: "Serverless text and vision-language chat models only; Hyperbolic's separate image, audio, and GPU endpoints are out of scope.", + }, // FREEZE 2026-07-10: exact serverless ids remain auth-gated/unverified. Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md. { id: "together", label: "Together", baseUrl: "https://api.together.xyz/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://api.together.xyz/settings/api-keys" }, { id: "fireworks", label: "Fireworks", baseUrl: "https://api.fireworks.ai/inference/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://fireworks.ai/account/api-keys" }, diff --git a/tests/fixtures/hyperbolic-models.json b/tests/fixtures/hyperbolic-models.json new file mode 100644 index 000000000..f2a488f78 --- /dev/null +++ b/tests/fixtures/hyperbolic-models.json @@ -0,0 +1,13 @@ +{ + "object": "list", + "data": [ + { + "id": "meta-llama/Llama-3.3-70B-Instruct", + "object": "model" + }, + { + "id": "Qwen/Qwen3-Coder-480B-A35B-Instruct", + "object": "model" + } + ] +} diff --git a/tests/hyperbolic-provider.test.ts b/tests/hyperbolic-provider.test.ts new file mode 100644 index 000000000..7cdaf36ef --- /dev/null +++ b/tests/hyperbolic-provider.test.ts @@ -0,0 +1,189 @@ +import { afterEach, describe, expect, test } from "bun:test"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import { createOpenAIChatAdapter } from "../src/adapters/openai-chat"; +import { gatherRoutedModels } from "../src/codex/catalog"; +import { clearModelCache } from "../src/codex/model-cache"; +import { buildInitProviders } from "../src/cli/init"; +import { buildModelsRequest } from "../src/oauth"; +import { KEY_LOGIN_PROVIDERS, validateApiKey } from "../src/oauth/key-providers"; +import { + deriveInitProviders, + deriveProviderPresets, + providerConfigSeed, +} from "../src/providers/derive"; +import { PROVIDER_REGISTRY, type ProviderRegistryEntry } from "../src/providers/registry"; +import { routedSlug } from "../src/providers/slug-codec"; +import { routeModel } from "../src/router"; +import type { OcxConfig, OcxProviderConfig } from "../src/types"; +import { withStubbedProviderFetch } from "./helpers/catalog-provider-fetch"; + +const FIXTURE = readFileSync(join(import.meta.dir, "fixtures/hyperbolic-models.json"), "utf8"); +const BASE_URL = "https://api.hyperbolic.xyz/v1"; +const API_KEY = "hyperbolic-test-key"; +const originalFetch = globalThis.fetch; + +afterEach(() => { + globalThis.fetch = originalFetch; + clearModelCache("hyperbolic"); +}); + +function registryEntry(): ProviderRegistryEntry { + const entry = PROVIDER_REGISTRY.find(row => row.id === "hyperbolic"); + if (!entry) throw new Error("missing hyperbolic registry entry"); + return entry; +} + +function providerConfig(overrides: Partial = {}): OcxConfig { + return { + port: 10100, + defaultProvider: "hyperbolic", + providers: { + hyperbolic: { + adapter: "openai-chat", + baseUrl: BASE_URL, + authMode: "key", + apiKey: API_KEY, + liveModels: true, + // Discovery stays fixture-only; this avoids platform-specific public-DNS classification. + allowPrivateNetwork: true, + ...overrides, + }, + }, + }; +} + +describe("Hyperbolic provider", () => { + test("registers a fixed OpenAI transport with bounded live discovery", () => { + expect(registryEntry()).toMatchObject({ + id: "hyperbolic", + label: "Hyperbolic", + adapter: "openai-chat", + baseUrl: BASE_URL, + authKind: "key", + dashboardUrl: "https://app.hyperbolic.ai", + liveModels: true, + preserveCustomDestination: true, + modelDiscovery: { + path: "models", + maxResponseBytes: 262_144, + maxModels: 256, + }, + }); + expect(registryEntry().note).toContain("image, audio, and GPU endpoints"); + }); + + test("derives CLI and dashboard presets without persisting registry trust policy", () => { + const entry = registryEntry(); + expect(buildInitProviders()).toEqual(deriveInitProviders()); + expect(KEY_LOGIN_PROVIDERS.hyperbolic).toMatchObject({ + adapter: "openai-chat", + baseUrl: BASE_URL, + dashboardUrl: entry.dashboardUrl, + liveModels: true, + }); + expect(buildInitProviders().find(row => row.id === "hyperbolic")).toMatchObject({ + kind: "key", + adapter: "openai-chat", + baseUrl: BASE_URL, + }); + expect(deriveProviderPresets().find(row => row.id === "hyperbolic")).toMatchObject({ + auth: "key", + dashboardUrl: entry.dashboardUrl, + }); + + const seed = providerConfigSeed(entry); + expect(seed).toMatchObject({ + adapter: "openai-chat", + baseUrl: BASE_URL, + authMode: "key", + liveModels: true, + }); + expect(seed).not.toHaveProperty("modelDiscovery"); + expect(seed).not.toHaveProperty("preserveCustomDestination"); + expect(KEY_LOGIN_PROVIDERS.hyperbolic).not.toHaveProperty("modelDiscovery"); + expect(KEY_LOGIN_PROVIDERS.hyperbolic).not.toHaveProperty("preserveCustomDestination"); + }); + + test("lists and validates models through the documented Bearer-authenticated endpoint", async () => { + expect(buildModelsRequest(providerConfig().providers.hyperbolic!, API_KEY, "hyperbolic")).toEqual({ + url: `${BASE_URL}/models`, + headers: { Authorization: `Bearer ${API_KEY}` }, + }); + + globalThis.fetch = (async (input, init) => { + expect(String(input)).toBe(`${BASE_URL}/models`); + expect(new Headers(init?.headers).get("authorization")).toBe(`Bearer ${API_KEY}`); + expect(init?.redirect).toBe("error"); + return new Response(FIXTURE, { + status: 200, + headers: { "content-type": "application/json" }, + }); + }) as typeof fetch; + + expect(await validateApiKey("hyperbolic", KEY_LOGIN_PROVIDERS.hyperbolic!, API_KEY)).toBe(true); + }); + + test("preserves slash ids and routes both selector forms", async () => { + globalThis.fetch = (async (input, init) => { + expect(String(input)).toBe(`${BASE_URL}/models`); + expect(new Headers(init?.headers).get("authorization")).toBe(`Bearer ${API_KEY}`); + expect(init?.redirect).toBe("manual"); + return new Response(FIXTURE, { + status: 200, + headers: { "content-type": "application/json" }, + }); + }) as typeof fetch; + + const config = withStubbedProviderFetch(providerConfig()); + const models = await gatherRoutedModels(config); + expect(models.filter(row => row.provider === "hyperbolic").map(row => row.id)).toEqual([ + "meta-llama/Llama-3.3-70B-Instruct", + "Qwen/Qwen3-Coder-480B-A35B-Instruct", + ]); + + const modelId = "meta-llama/Llama-3.3-70B-Instruct"; + expect(routeModel(config, `hyperbolic/${modelId}`).modelId).toBe(modelId); + expect(routeModel(config, routedSlug("hyperbolic", modelId)).modelId).toBe(modelId); + }); + + test("routes chat completions to the fixed inference host", () => { + const modelId = "meta-llama/Llama-3.3-70B-Instruct"; + const route = routeModel(providerConfig(), `hyperbolic/${modelId}`); + const request = createOpenAIChatAdapter(route.provider).buildRequest({ + modelId: route.modelId, + context: { messages: [{ role: "user", content: "ping", timestamp: 0 }] }, + stream: true, + options: {}, + }); + const body = JSON.parse(String(request.body)) as Record; + + expect(request.url).toBe(`${BASE_URL}/chat/completions`); + expect(request.headers.Authorization).toBe(`Bearer ${API_KEY}`); + expect(body.model).toBe(modelId); + }); + + test("does not retarget older same-named custom providers", () => { + const customConfig = providerConfig({ baseUrl: "https://custom.example/v1" }); + const route = routeModel(customConfig, "hyperbolic/custom-model"); + expect(route.provider).toMatchObject({ + adapter: "openai-chat", + baseUrl: "https://custom.example/v1", + authMode: "key", + }); + expect(buildModelsRequest(customConfig.providers.hyperbolic!, "custom-key", "hyperbolic")).toEqual({ + url: "https://custom.example/v1/models", + headers: { Authorization: "Bearer custom-key" }, + }); + + const customAdapter = routeModel(providerConfig({ + adapter: "anthropic", + baseUrl: "https://custom.example/anthropic", + }), "hyperbolic/custom-model"); + expect(customAdapter.provider).toMatchObject({ + adapter: "anthropic", + baseUrl: "https://custom.example/anthropic", + authMode: "key", + }); + }); +}); diff --git a/tests/provider-registry-parity.test.ts b/tests/provider-registry-parity.test.ts index d2f7d5735..03751b0fb 100644 --- a/tests/provider-registry-parity.test.ts +++ b/tests/provider-registry-parity.test.ts @@ -31,7 +31,7 @@ function nativeTemplate(): Record { const EXPECTED_KEY_PROVIDER_IDS = [ "anthropic-apikey", "openai-apikey", "umans", "opencode-go", "neuralwatt", "openrouter", "orcarouter", "bizrouter", "groq", "google", "google-vertex", "azure-openai", - "deepseek", "cerebras", "together", "fireworks", "firepass", "moonshot", + "deepseek", "cerebras", "hyperbolic", "together", "fireworks", "firepass", "moonshot", "huggingface", "nvidia", "venice", "zai", "zhipu-bigmodel", "nanogpt", "synthetic", "siliconflow", "qwen-cloud", "tencent-coding-plan", "qianfan", "alibaba", "alibaba-token-plan", "alibaba-token-plan-intl", "parallel", "zenmux", "litellm", "ollama-cloud", "mistral", "minimax", "minimax-cn", "kimi-code", "opencode-zen", "vercel-ai-gateway", From dbf4bd10c9ca35bb9d0f915c4317156216cbb5af Mon Sep 17 00:00:00 2001 From: olddonkey Date: Thu, 30 Jul 2026 14:54:18 -0700 Subject: [PATCH 2/2] docs(providers): align OAuth catalog translations --- docs-site/src/content/docs/ja/guides/providers.md | 4 ++-- docs-site/src/content/docs/ko/guides/providers.md | 2 +- docs-site/src/content/docs/zh-cn/guides/providers.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs-site/src/content/docs/ja/guides/providers.md b/docs-site/src/content/docs/ja/guides/providers.md index 97098fd4e..62107bc78 100644 --- a/docs-site/src/content/docs/ja/guides/providers.md +++ b/docs-site/src/content/docs/ja/guides/providers.md @@ -60,7 +60,7 @@ ChatGPT パススルーカタログには GPT-5.6 Sol/Terra/Luna の名前空間 ## 2. アカウントログイン(OAuth) -OAuth ログインを使うプロバイダープリセットは 6 つです。認証情報は +OAuth ログインを使うプロバイダープリセットは 7 つです。認証情報は `~/.opencodex/auth.json` に保存され、自動更新されます。ログイン CLI は `chatgpt` も受け付けます。 このコマンドは ChatGPT 認証情報を発行し `forward` モードのプロバイダーエントリを作成します。 @@ -146,7 +146,7 @@ opencodex には組み込みプリセットが 61 個含まれています。キ | SiliconFlow | `https://api.siliconflow.cn/v1` | | Xiaomi MiMo | `https://api.xiaomimimo.com/anthropic` | | Kilo | `https://api.kilo.ai/api/gateway` | -| GitHub Copilot · GitLab Duo | `https://api.githubcopilot.com` · `https://cloud.gitlab.com/ai/v1/proxy/openai/v1` | +| GitLab Duo | `https://cloud.gitlab.com/ai/v1/proxy/openai/v1` | | Cloudflare AI Gateway | `https://gateway.ai.cloudflare.com/v1/{account-id}/{gateway}/anthropic` | | …その他多数 | opencode zen、Vercel AI Gateway、Venice、NanoGPT、Synthetic、Qianfan、Alibaba、Parallel、ZenMux、LiteLLM | diff --git a/docs-site/src/content/docs/ko/guides/providers.md b/docs-site/src/content/docs/ko/guides/providers.md index 0a7806161..d1788cbc6 100644 --- a/docs-site/src/content/docs/ko/guides/providers.md +++ b/docs-site/src/content/docs/ko/guides/providers.md @@ -146,7 +146,7 @@ opencodex에는 빌트인 프리셋이 61개 들어 있습니다. 키 방식 50 | SiliconFlow | `https://api.siliconflow.cn/v1` | | Xiaomi MiMo | `https://api.xiaomimimo.com/anthropic` | | Kilo | `https://api.kilo.ai/api/gateway` | -| GitHub Copilot · GitLab Duo | `https://api.githubcopilot.com` · `https://cloud.gitlab.com/ai/v1/proxy/openai/v1` | +| GitLab Duo | `https://cloud.gitlab.com/ai/v1/proxy/openai/v1` | | Cloudflare AI Gateway | `https://gateway.ai.cloudflare.com/v1/{account-id}/{gateway}/anthropic` | | …그 외 다수 | opencode zen, Vercel AI Gateway, Venice, NanoGPT, Synthetic, Qianfan, Alibaba, Parallel, ZenMux, LiteLLM | diff --git a/docs-site/src/content/docs/zh-cn/guides/providers.md b/docs-site/src/content/docs/zh-cn/guides/providers.md index 53a046d06..ca5c95cd8 100644 --- a/docs-site/src/content/docs/zh-cn/guides/providers.md +++ b/docs-site/src/content/docs/zh-cn/guides/providers.md @@ -138,7 +138,7 @@ ChatGPT 转发预设。仪表盘的 **Add provider** 选择器会打开密钥提 | SiliconFlow | `https://api.siliconflow.cn/v1` | | Xiaomi MiMo | `https://api.xiaomimimo.com/anthropic` | | Kilo | `https://api.kilo.ai/api/gateway` | -| GitHub Copilot · GitLab Duo | `https://api.githubcopilot.com` · `https://cloud.gitlab.com/ai/v1/proxy/openai/v1` | +| GitLab Duo | `https://cloud.gitlab.com/ai/v1/proxy/openai/v1` | | Cloudflare AI Gateway | `https://gateway.ai.cloudflare.com/v1/{account-id}/{gateway}/anthropic` | | ……以及更多 | opencode zen、Vercel AI Gateway、Venice、NanoGPT、Synthetic、Qianfan、Alibaba、Parallel、ZenMux、LiteLLM |