From f4c48c47db0cbd77812246c3e6488a98180a70f3 Mon Sep 17 00:00:00 2001 From: JUN Date: Tue, 29 Sep 2026 21:28:19 +0900 Subject: [PATCH 1/4] feat(sponsors): TokenLab Standard sponsor placement, README row and docs TokenLab is the third Standard sponsor under SPONSORS.md, after OrcaRouter and PackyCode, so it takes the third row of the Standard table and is pinned with the other sponsors in the picker (Main before Standard, then alphabetical by label). The tokenlab registry entry gains the sponsor field; routing, discovery and defaults never read it. The dashboard sponsor card now looks brands up in a table, so a sponsor preset without a row renders nothing instead of borrowing another sponsor's copy. Adds the sponsor-supplied logos (light and dark lockups via ), the translated README rows and manifest hashes, the picker mark for the GUI and desktop shell, localized card copy in every dashboard locale, a sponsor link in the provider guide, and fixes two stale SPONSORS.md sentences. --- README.md | 4 ++ SPONSORS.md | 9 ++- assets/sponsors/tokenlab-dark.png | Bin 0 -> 6089 bytes assets/sponsors/tokenlab-light.png | Bin 0 -> 6234 bytes desktop/src-tauri/src/provider_icons.rs | 3 + .../260929_tokenlab_sponsor/000_roadmap.md | 13 ++++ .../260929_tokenlab_sponsor/010_merge_6221.md | 37 ++++++++++ .../260929_tokenlab_sponsor/020_sponsor_pr.md | 63 ++++++++++++++++++ .../src/content/docs/guides/providers.md | 7 +- gui/public/provider-icons/README.md | 9 +++ gui/public/provider-icons/tokenlab.svg | 8 +++ .../provider-workspace/ProviderSponsor.tsx | 34 +++++++--- gui/src/i18n/de.ts | 2 + gui/src/i18n/en.ts | 2 + gui/src/i18n/fr.ts | 2 + gui/src/i18n/ja.ts | 2 + gui/src/i18n/ko.ts | 2 + gui/src/i18n/ru.ts | 2 + gui/src/i18n/tr.ts | 2 + gui/src/i18n/vi.ts | 2 + gui/src/i18n/zh-TW.ts | 2 + gui/src/i18n/zh.ts | 2 + gui/src/provider-icons.ts | 3 + gui/src/styles/provider-workspace-shell.css | 7 +- .../provider-catalog-sponsor-pinning.test.ts | 3 +- gui/tests/provider-sponsor-overview.test.tsx | 21 ++++++ package.json | 2 + readme/README.fr.md | 4 ++ readme/README.ja.md | 4 ++ readme/README.ko.md | 4 ++ readme/README.ru.md | 4 ++ readme/README.tr.md | 4 ++ readme/README.zh-CN.md | 4 ++ readme/README.zh-TW.md | 4 ++ readme/i18n-manifest.json | 14 ++-- src/providers/registry/entries-extended.ts | 3 + structure/dashboard-and-usage.md | 6 +- tests/providers/tokenlab-provider.test.ts | 4 +- 38 files changed, 267 insertions(+), 31 deletions(-) create mode 100644 assets/sponsors/tokenlab-dark.png create mode 100644 assets/sponsors/tokenlab-light.png create mode 100644 devlog/_plan/260929_tokenlab_sponsor/000_roadmap.md create mode 100644 devlog/_plan/260929_tokenlab_sponsor/010_merge_6221.md create mode 100644 devlog/_plan/260929_tokenlab_sponsor/020_sponsor_pr.md create mode 100644 gui/public/provider-icons/tokenlab.svg diff --git a/README.md b/README.md index 4fdc0868de3..65acb9547e4 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,10 @@ See [SPONSORS.md](./SPONSORS.md). PackyCode Thanks to PackyCode for sponsoring this project! PackyCode is a stable, high-performance API relay provider, offering relay services for Claude Code, Codex, Gemini, and more. With automatic failover, smart routing, and unlimited concurrency, it turns AI into a real productivity tool. Register via this link and get started! Pick PackyCode in the Add provider picker or run ocx provider add packycode.
PackyCode 是一家稳定、高效的 API 中转服务商,提供 Claude Code、Codex、Gemini 等多种中转服务。具备自动故障转移、智能路由和无限并发等多种功能,让 AI 编程成为真正的生产力工具。点此链接注册,立即开始使用! + +TokenLab +Thanks to TokenLab for sponsoring this project! TokenLab gives coding agents one API key for leading models, with Responses, Chat Completions, streaming, and tool calling. Choose your delivery mode and pay as you go. Pick TokenLab in the Add provider picker or run ocx provider add tokenlab. + diff --git a/SPONSORS.md b/SPONSORS.md index 76a54c377b7..5eed39bd66e 100644 --- a/SPONSORS.md +++ b/SPONSORS.md @@ -47,8 +47,8 @@ sponsor receives: policy. A second-language blurb (for example Chinese) may run alongside the English one. - A built-in provider preset (`ocx provider add `) shipped in a public npm release, listed near the top of the provider picker in the dashboard and CLI and marked as a sponsor - there. (The registry field and picker ordering that back this land with the first sponsor - preset; today the picker follows registry order.) + there. The dashboard picker lists sponsor rows Main before Standard, then alphabetically by + label, an order no sponsor can buy. - A detailed entry on the [providers page](https://opencodex.me/guides/providers/) of the docs site. - Maintenance: if a release breaks the preset or its adapter, the maintainer fixes it; issues @@ -66,9 +66,8 @@ and the placements themselves: The README says nothing else about sponsorship; tiers, pricing, and contact channels live only on this page. -The translated READMEs under [`readme/`](./readme) carry one linking line right after their -own quick-start block instead of duplicating the section, so a sponsor change is one edit in -English. +The translated READMEs under [`readme/`](./readme) carry the same sponsor section, with each +row's thanks line and blurb translated, so a sponsor change updates every README together. ## Pricing diff --git a/assets/sponsors/tokenlab-dark.png b/assets/sponsors/tokenlab-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..7c1ef48dac96524bef0e09715f480fcc60c4c184 GIT binary patch literal 6089 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 Mp|)n#CU;Q(1ysqV1ONa4 literal 0 HcmV?d00001 diff --git a/assets/sponsors/tokenlab-light.png b/assets/sponsors/tokenlab-light.png new file mode 100644 index 0000000000000000000000000000000000000000..37a0f17ef694210cb746c1026b65da99b3312ad1 GIT binary patch literal 6234 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 &'static str { | "packycode.svg" | "siliconflow.svg" | "synthetic.svg" + | "tokenlab.svg" | "vercel-ai-gateway-color.svg" | "zenmux.svg" => "mask", "baseten.svg" | "kilo.svg" | "sambanova.svg" | "venice.svg" | "zai.svg" => "plate", @@ -200,6 +202,7 @@ fn svg(file: &str) -> Option<&'static str> { "stepfun-color.svg" => svg!("stepfun-color.svg"), "synthetic.svg" => svg!("synthetic.svg"), "together.svg" => svg!("together.svg"), + "tokenlab.svg" => svg!("tokenlab.svg"), "umans.svg" => svg!("umans.svg"), "venice.svg" => svg!("venice.svg"), "vercel-ai-gateway-color.svg" => svg!("vercel-ai-gateway-color.svg"), diff --git a/devlog/_plan/260929_tokenlab_sponsor/000_roadmap.md b/devlog/_plan/260929_tokenlab_sponsor/000_roadmap.md new file mode 100644 index 00000000000..3d14bac63ca --- /dev/null +++ b/devlog/_plan/260929_tokenlab_sponsor/000_roadmap.md @@ -0,0 +1,13 @@ +# TokenLab — preset merge and third Standard sponsor + +TokenLab (TOKENLAB AI INC.) signed up as a Standard sponsor on 2026-09-29 (DocuSign envelope sent +21:08 KST; USD 1,200 / 3 months, term starts at the npm release that ships the preset). Two units: + +| Doc | Work phase | Outcome | +|-----|-----------|---------| +| [010](./010_merge_6221.md) | wp1 | #6221 (fork `hedging8563`, ordinary API-key preset) merged into `dev` on green exact-head CI | +| [020](./020_sponsor_pr.md) | wp2 | Separate maintainer PR: sponsor field, dashboard sponsor surface, README row #3, docs, assets | + +The sponsor PR stays unmerged for owner review. The Responses-first proposal in TokenLab's asset pack +(`integration-notes.md`) is out of scope for both units: it changes the upstream protocol and needs +its own evidence and PR. diff --git a/devlog/_plan/260929_tokenlab_sponsor/010_merge_6221.md b/devlog/_plan/260929_tokenlab_sponsor/010_merge_6221.md new file mode 100644 index 00000000000..789fa1aa080 --- /dev/null +++ b/devlog/_plan/260929_tokenlab_sponsor/010_merge_6221.md @@ -0,0 +1,37 @@ +# 010 — Merge #6221 (TokenLab API-key preset) + +## State at plan time + +- Head repo `hedging8563/opencodex`, branch `codex/tokenlab-provider`, draft, `maintainerCanModify`. +- Branch updated onto `dev` with `gh pr update-branch` → head `6a9e571202`. +- Fork runs `Cross-platform CI` (36566988054) and `React Doctor` (36566988033) approved by the maintainer. +- Admission gap recorded in the PR body: routing/resale authorization evidence. The owner's + sponsorship agreement with the operator (§2.2: sourcing and delivery mode are TokenLab's + responsibility, not audited or guaranteed by opencodex) is the maintainer decision that closes it; + no upstream endorsement claim is added. + +## Steps + +0. Head moved to `fb565b7c48`: the `dev` union failed `docs-provider-preset-counts` (16 assertions, + localized 99/82 counts). Recounted the anchored lines; the test passes locally 18/18. +1. Wait for every required check on `6a9e571202` to complete successfully. A red, skipped or + older-head result blocks merge; fix forward only if the failure is in this diff. +2. Mark the PR ready (`gh pr ready 6221`). +3. Post a maintainer-integration comment: owner instruction, exact head SHA, check list, and the + admission decision above. +4. Squash-merge with the admin bypass (`gh pr merge 6221 --squash --admin`); the squash keeps the + PR author as commit author. +5. Verify `dev` contains the squash commit and `src/providers/registry/entries-extended.ts` carries + the `tokenlab` entry. + +## Not in this unit + +Sponsor field, README, logos — see 020. + +## Outcome (2026-09-29) + +Merged as `f6cddd7d69` at 12:51 UTC from head `fb565b7c48`: 24 checks passed, 7 skipped by path +conditions (Cross-platform CI 36567400620, React Doctor). CodeRabbit's assertive review found nothing +actionable. The readiness gate re-drafted the PR on `ready`; the maintainer ticked the last two boxes +after that review and recorded it on the PR. A full local `bun run test` cannot run from a worktree +under `~/.codex`: the harness refuses to delete temporary directories inside the real Codex home. diff --git a/devlog/_plan/260929_tokenlab_sponsor/020_sponsor_pr.md b/devlog/_plan/260929_tokenlab_sponsor/020_sponsor_pr.md new file mode 100644 index 00000000000..0cbee8371cf --- /dev/null +++ b/devlog/_plan/260929_tokenlab_sponsor/020_sponsor_pr.md @@ -0,0 +1,63 @@ +# 020 — TokenLab third Standard sponsor PR + +Branch `codex/tokenlab-sponsor`, started on the #6221 head and rebased onto `dev` after 010 lands. +Pattern: #3914 (OrcaRouter, mechanism) and #3915 (PackyCode, second sponsor). Assets come from +TokenLab's corrected pack (`TokenLab-OpenCodex-assets-corrected.zip`), artwork unchanged. + +## Diff + +- `src/providers/registry/entries-extended.ts` — the `tokenlab` entry gains + `sponsor: { tier: "standard", url: "https://tokenlab.sh/?utm_source=opencodex&utm_medium=readme" }` + with a comment naming SPONSORS.md and the signing date. No routing, default or discovery change. +- `assets/sponsors/tokenlab-light.png`, `assets/sponsors/tokenlab-dark.png` — the pack's 500×125 + lockups, unchanged (`-light` = for light page backgrounds, as in the pack). Listed in + `package.json` `files` beside the OrcaRouter/PackyCode logos. +- `README.md` — third row of the `sponsors:standard` table, in signing order after PackyCode. + Logo in `` with a `prefers-color-scheme: dark` source so the dark lockup shows in + GitHub dark mode; the `` fallback is the light lockup (npm). Text: "Thanks to TokenLab for + sponsoring this project!", the sponsor's English blurb verbatim, then the picker / + `ocx provider add tokenlab` line. +- `readme/README.{ko,ja,zh-CN,zh-TW,ru,fr,tr}.md` — the same row, translated like the existing + rows, image paths `../assets/...`. Keep the README drift gate green. +- `gui/public/provider-icons/tokenlab.svg` — the pack's symbol mark; `gui/src/provider-icons.ts` + maps `tokenlab` to it, sets the display name, and applies dark-mode treatment like other + monochrome marks. +- `gui/src/components/provider-workspace/ProviderSponsor.tsx` — replace the hardcoded + OrcaRouter/PackyCode ternaries with a small brand table so a sponsor is one row; add TokenLab. + i18n keys `pws.sponsor.tokenlabTitle` / `pws.sponsor.tokenlabDescription` in every + `gui/src/i18n/*.ts` locale (the closed catalog must stay exhaustive). +- `docs-site/src/content/docs/guides/providers.md` — sponsor paragraph after PackyCode, and the + base-URL table row if #6221 has not already added it. Translated locales must not contradict it. +- Tests — `tests/providers/sponsor-presets.test.ts` stays generic; the GUI sponsor overview test + covers the TokenLab card. Respect the file-size ratchet: move rather than grow capped files. + +## Verification + +## Audit folds (reviewer, NEAR-PASS) + +- `desktop/src-tauri/src/provider_icons.rs`: `("tokenlab", "tokenlab.svg")` in `ALIASES`, the same paint + arm as `MASKED_PROVIDER_ICONS`, and `svg!("tokenlab.svg")` (`gui/tests/provider-icons-native.test.ts`). +- `readme/i18n-manifest.json`: refresh all seven `sourceSha256` values to the new LF-normalized README hash + (`docs-readme-translation-parity.test.ts`). +- `` is gate-compatible: only `src=`/`href=` are asset-tracked, so each locale's `` must be + `../assets/sponsors/tokenlab-light.png`; include the dark `` too. +- Icon provenance row in `gui/public/provider-icons/README.md`; `structure/dashboard-and-usage.md` owns the + `ProviderSponsor` description — update it for the brand table. +- Docs: extend the existing TokenLab section in `guides/providers.md` with the sponsor link; do not add a + second paragraph. +- `SPONSORS.md`: fix the stale "picker follows registry order" and "translated READMEs carry one linking + line" sentences. +- Branch: after #6221 squashes, move only the sponsor commits onto `dev` + (`git rebase --onto origin/dev fb565b7c48`). + +## Verification (commands) + +Focused: sponsor-presets, provider-registry-parity, tokenlab-provider, README drift/translation +gates, GUI sponsor tests, `bun run typecheck`, `bun run lint:gui`, `bun run privacy:scan`, +`bun run structure:check`, `bun run build:gui`. Then `bun run test` or the documented focused +exception. Screenshot of the dashboard sponsor card and picker uploaded to the `pr-assets` branch +and linked by SHA in the PR description, never committed to the PR branch. + +## Out of scope + +Responses-first adapter and the `X-TokenLab-Delivery-Policy` header (pack `integration-notes.md`). diff --git a/docs-site/src/content/docs/guides/providers.md b/docs-site/src/content/docs/guides/providers.md index 821a5b81178..ca0430e7f92 100644 --- a/docs-site/src/content/docs/guides/providers.md +++ b/docs-site/src/content/docs/guides/providers.md @@ -553,7 +553,9 @@ region-pinned EU routes, is at [opper.ai/models](https://opper.ai/models). Opper | Cloudflare AI Gateway | `https://gateway.ai.cloudflare.com/v1/{account-id}/{gateway}/anthropic` | | …and more | opencode zen, Vercel AI Gateway, Venice, NanoGPT, Synthetic, Qianfan, Alibaba, Parallel, ZenMux, LiteLLM | -**TokenLab** is an OpenAI-compatible API gateway operated by TOKENLAB AI INC. +**TokenLab** ([sponsor](https://github.com/lidge-jun/opencodex/blob/main/SPONSORS.md)) is an +OpenAI-compatible API gateway at [tokenlab.sh](https://tokenlab.sh/?utm_source=opencodex&utm_medium=readme), +operated by TOKENLAB AI INC. Create a workspace [API key](https://tokenlab.sh/dashboard/api?tab=keys), then run `ocx provider add tokenlab` or select **TokenLab** in the dashboard's **Add provider** picker. The preset uses [Chat Completions](https://docs.tokenlab.sh/quickstart) and discovers models at @@ -566,7 +568,8 @@ policy. Use a valid key with a funded workspace for inference. `gpt-5.6-terra` i default; choose another discovered model if your key does not allow it. The provider and model prefixes remain separate: `tokenlab/gpt-5.6-terra` sends `gpt-5.6-terra` upstream. TokenLab's [terms](https://tokenlab.sh/tos) and [privacy policy](https://tokenlab.sh/privacy-policy) -apply to requests sent to this service. +apply to requests sent to this service. The preset pins the row near the top of the Add provider +picker and marks it as a sponsor, and nothing else about routing or defaults changes. The MiniMax and MiniMax (CN) provider cards can also show Coding Plan quota when the configured key has an active plan. The dashboard reads the plan's 5-hour window and, when present, weekly diff --git a/gui/public/provider-icons/README.md b/gui/public/provider-icons/README.md index 7efbbbac919..e53cfda2211 100644 --- a/gui/public/provider-icons/README.md +++ b/gui/public/provider-icons/README.md @@ -382,3 +382,12 @@ committing it. gradient is the brand, so it must never be masked. - `factory-droid.svg` — Factory Docs favicon, fetched 2026-09-28 from `https://docs.factory.ai/favicon.svg`; unmodified first-party asset for Factory Droid. + +## TokenLab (2026-09-29) + +- `tokenlab.svg` — TokenLab's "Specimen Split" symbol, `logos/tokenlab-symbol.svg` from the + sponsor asset pack TokenLab supplied for its Standard sponsorship (`SPONSORS.md`), committed + byte for byte (416 bytes, MD5 `fcb8c1325a34ed52b1dc89eabf08a0e6`), ``/`<desc>` included + since they do not affect rendering. `viewBox="0 0 48 48"`, one `#151714` ink, so it is + **masked** like `packycode.svg`: as an image it vanishes on the dark tile. The pack's reverse + symbol (`#F5F5F1`) is the same geometry and is not needed once the mark follows the theme. diff --git a/gui/public/provider-icons/tokenlab.svg b/gui/public/provider-icons/tokenlab.svg new file mode 100644 index 00000000000..6c193a9214e --- /dev/null +++ b/gui/public/provider-icons/tokenlab.svg @@ -0,0 +1,8 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" role="img" aria-labelledby="tokenlab-symbol-title tokenlab-symbol-desc"> + <title id="tokenlab-symbol-title">TokenLab + Specimen Split symbol, positive master for sizes from 32 to 96 pixels. + + + + + diff --git a/gui/src/components/provider-workspace/ProviderSponsor.tsx b/gui/src/components/provider-workspace/ProviderSponsor.tsx index 6b52252b91a..621ac4a95b0 100644 --- a/gui/src/components/provider-workspace/ProviderSponsor.tsx +++ b/gui/src/components/provider-workspace/ProviderSponsor.tsx @@ -1,8 +1,26 @@ -import { useT } from "../../i18n/shared"; +import { useT, type TKey } from "../../i18n/shared"; import { IconExternal } from "../../icons"; import type { WorkspaceItem } from "../../provider-workspace/catalog"; import { matchingWorkspacePreset, type CatalogPreset } from "../provider-catalog/provider-presets"; +type SponsorBrand = { name: string; title: TKey; description: TKey }; + +const ORCAROUTER: SponsorBrand = { + name: "OrcaRouter", title: "pws.sponsor.orcaTitle", description: "pws.sponsor.orcaDescription", +}; + +/** + * One row per sponsor preset id. The registry `sponsor` field decides whether a card shows; + * this table only supplies the brand name and localized copy, so a preset flagged as a sponsor + * without a row here still renders nothing rather than borrowing another sponsor's words. + */ +const SPONSOR_BRANDS: Readonly> = { + orcarouter: ORCAROUTER, + "orcarouter-oauth": ORCAROUTER, + packycode: { name: "PackyCode", title: "pws.sponsor.packyTitle", description: "pws.sponsor.packyDescription" }, + tokenlab: { name: "TokenLab", title: "pws.sponsor.tokenlabTitle", description: "pws.sponsor.tokenlabDescription" }, +}; + function webLink(value?: string): string | undefined { if (!value) return undefined; try { @@ -16,25 +34,23 @@ function webLink(value?: string): string | undefined { export default function ProviderSponsor({ item, preset }: { item: WorkspaceItem; preset?: CatalogPreset }) { const t = useT(); if (!preset?.sponsor || !matchingWorkspacePreset(item, [preset])) return null; - const brand = preset.id === "orcarouter" || preset.id === "orcarouter-oauth" - ? "OrcaRouter" : preset.id === "packycode" ? "PackyCode" : undefined; + const brand = Object.hasOwn(SPONSOR_BRANDS, preset.id) ? SPONSOR_BRANDS[preset.id] : undefined; if (!brand) return null; - const orca = brand === "OrcaRouter"; const visit = webLink(preset.sponsorUrl); const dashboard = webLink(preset.dashboardUrl); - return
+ return
- {brand} + {brand.name} {t("modal.badge.sponsor")}
-

{t(orca ? "pws.sponsor.orcaTitle" : "pws.sponsor.packyTitle")}

-

{t(orca ? "pws.sponsor.orcaDescription" : "pws.sponsor.packyDescription")}

+

{t(brand.title)}

+

{t(brand.description)}

{(visit || dashboard) &&
{visit && - {t("pws.sponsor.visit", { provider: brand })}} {dashboard && dashboard !== visit && {t("pws.sponsor.console")} { + const html = render(tokenlab, configured(tokenlab)); + expect(html).toContain("TokenLab"); + expect(html).toContain("One API key for leading models"); + expect(html).not.toContain("A model for every prompt"); + expect(html).toContain('href="https://tokenlab.sh/?utm_source=opencodex&utm_medium=readme"'); + expect(html).toContain('href="https://tokenlab.sh/dashboard/api?tab=keys"'); + expect(html.match(/ { + const unknown = { ...tokenlab, id: "future-sponsor" }; + expect(render(unknown, configured(unknown))).toBe(""); +}); + test("missing and non-sponsor presets render nothing; non-web links never become anchors", () => { expect(render()).toBe(""); expect(render({ ...orca, sponsor: undefined })).toBe(""); diff --git a/package.json b/package.json index f3f51f4cbca..ab92796acfa 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,8 @@ "assets/codex-app-picker.png", "assets/sponsors/orcarouter.png", "assets/sponsors/packycode.png", + "assets/sponsors/tokenlab-light.png", + "assets/sponsors/tokenlab-dark.png", "README.md", "SPONSORS.md", "AGENTS_INSTALL.md", diff --git a/readme/README.fr.md b/readme/README.fr.md index 9329dfd2def..dce2932ea0e 100644 --- a/readme/README.fr.md +++ b/readme/README.fr.md @@ -161,6 +161,10 @@ Voir [SPONSORS.md](../SPONSORS.md). PackyCode Merci à PackyCode pour son soutien à ce projet ! PackyCode est un fournisseur de relais API stable et performant, qui propose des services de relais pour Claude Code, Codex, Gemini et d'autres. Grâce au basculement automatique, au routage intelligent et à une concurrence illimitée, il fait de l'IA un véritable outil de productivité. Inscrivez-vous via ce lien et commencez ! Choisissez PackyCode dans le sélecteur Add provider ou exécutez ocx provider add packycode.
PackyCode 是一家稳定、高效的 API 中转服务商,提供 Claude Code、Codex、Gemini 等多种中转服务。具备自动故障转移、智能路由和无限并发等多种功能,让 AI 编程成为真正的生产力工具。点此链接注册,立即开始使用! + +TokenLab +Merci à TokenLab pour son soutien à ce projet ! TokenLab donne aux agents de code une seule clé API pour les principaux modèles, avec Responses, Chat Completions, le streaming et l'appel d'outils. Choisissez votre mode de livraison et payez à l'usage. Choisissez TokenLab dans le sélecteur Add provider ou exécutez ocx provider add tokenlab. + diff --git a/readme/README.ja.md b/readme/README.ja.md index 43726a65649..5e41ec22db8 100644 --- a/readme/README.ja.md +++ b/readme/README.ja.md @@ -156,6 +156,10 @@ failover、アカウントの除外、affinity の失効、401/403 や 429 か PackyCode このプロジェクトを支援してくださる PackyCode に感謝します。PackyCode は安定した高性能の API リレープロバイダーで、Claude Code、Codex、Gemini などのリレーを提供しています。自動 failover、スマートルーティング、無制限の同時実行によって、AI を実際の生産性ツールに変えます。このリンクから登録してすぐに始めてください。Add provider ピッカーで PackyCode を選ぶか ocx provider add packycode を実行してください。
PackyCode 是一家稳定、高效的 API 中转服务商,提供 Claude Code、Codex、Gemini 等多种中转服务。具备自动故障转移、智能路由和无限并发等多种功能,让 AI 编程成为真正的生产力工具。点此链接注册,立即开始使用! + +TokenLab +このプロジェクトを支援してくださる TokenLab に感謝します。TokenLab はコーディングエージェントに主要モデル向けの API キーを 1 つだけ提供し、Responses、Chat Completions、ストリーミング、ツール呼び出しに対応しています。配信モードを選び、使った分だけ支払えます。Add provider ピッカーで TokenLab を選ぶか ocx provider add tokenlab を実行してください。 + diff --git a/readme/README.ko.md b/readme/README.ko.md index 4facdfd7de1..2dec3243405 100644 --- a/readme/README.ko.md +++ b/readme/README.ko.md @@ -153,6 +153,10 @@ Codex Desktop 로그인처럼 다른 계정이 소진된 뒤에만 쓰고 싶은 PackyCode PackyCode의 후원에 감사합니다. PackyCode는 안정적인 고성능 API 릴레이 프로바이더로, Claude Code, Codex, Gemini 등의 릴레이를 제공합니다. 자동 failover, 스마트 라우팅, 무제한 동시성으로 AI를 실제 생산성 도구로 만듭니다. 이 링크로 등록하고 바로 시작하세요. Add provider 선택기에서 PackyCode를 고르거나 ocx provider add packycode를 실행하세요.
PackyCode 是一家稳定、高效的 API 中转服务商,提供 Claude Code、Codex、Gemini 等多种中转服务。具备自动故障转移、智能路由和无限并发等多种功能,让 AI 编程成为真正的生产力工具。点此链接注册,立即开始使用! + +TokenLab +TokenLab의 후원에 감사합니다. TokenLab은 코딩 에이전트에게 주요 모델용 API 키 하나를 제공하며, Responses, Chat Completions, 스트리밍, 도구 호출을 지원합니다. 전달 모드를 고르고 쓴 만큼 결제하세요. Add provider 선택기에서 TokenLab을 고르거나 ocx provider add tokenlab을 실행하세요. + diff --git a/readme/README.ru.md b/readme/README.ru.md index c274d4f632b..3f5558b66a8 100644 --- a/readme/README.ru.md +++ b/readme/README.ru.md @@ -164,6 +164,10 @@ opencodex также умеет управлять **пулом аккаунто PackyCode Благодарим PackyCode за спонсорскую поддержку проекта! PackyCode — стабильный высокопроизводительный API-релей, предоставляющий релей-сервисы для Claude Code, Codex, Gemini и других. Автоматический failover, умная маршрутизация и неограниченная конкурентность превращают AI в настоящий инструмент продуктивности. Зарегистрируйтесь по этой ссылке и начните работу! Выберите PackyCode в селекторе Add provider или выполните ocx provider add packycode.
PackyCode 是一家稳定、高效的 API 中转服务商,提供 Claude Code、Codex、Gemini 等多种中转服务。具备自动故障转移、智能路由和无限并发等多种功能,让 AI 编程成为真正的生产力工具。点此链接注册,立即开始使用! + +TokenLab +Благодарим TokenLab за спонсорскую поддержку проекта! TokenLab даёт агентам для программирования один API-ключ для ведущих моделей с поддержкой Responses, Chat Completions, стриминга и вызова инструментов. Выберите режим доставки и платите по мере использования. Выберите TokenLab в селекторе Add provider или выполните ocx provider add tokenlab. + diff --git a/readme/README.tr.md b/readme/README.tr.md index 1a5e35ae823..a16fa0ce936 100644 --- a/readme/README.tr.md +++ b/readme/README.tr.md @@ -160,6 +160,10 @@ Her yukarı akış protokol değişiminde opencodex'in bakımını sürdürebilm PackyCode Bu projeye sponsor olduğu için PackyCode'a teşekkürler! PackyCode, Claude Code, Codex, Gemini ve daha fazlası için aktarma hizmeti sunan istikrarlı ve yüksek başarımlı bir API aktarma sağlayıcısıdır. Otomatik failover, akıllı yönlendirme ve sınırsız eşzamanlılıkla yapay zekâyı gerçek bir üretkenlik aracına dönüştürür. Bu bağlantıdan kaydolun ve hemen başlayın! Add provider seçicisinden PackyCode seçin ya da ocx provider add packycode çalıştırın.
PackyCode 是一家稳定、高效的 API 中转服务商,提供 Claude Code、Codex、Gemini 等多种中转服务。具备自动故障转移、智能路由和无限并发等多种功能,让 AI 编程成为真正的生产力工具。点此链接注册,立即开始使用! + +TokenLab +Bu projeye sponsor olduğu için TokenLab'a teşekkürler! TokenLab, kodlama ajanlarına önde gelen modeller için tek bir API anahtarı sunar; Responses, Chat Completions, akış ve araç çağırma desteklenir. Teslimat modunuzu seçin, kullandıkça ödeyin. Add provider seçicisinden TokenLab seçin ya da ocx provider add tokenlab çalıştırın. + diff --git a/readme/README.zh-CN.md b/readme/README.zh-CN.md index 2394f99ca9e..f4765a81273 100644 --- a/readme/README.zh-CN.md +++ b/readme/README.zh-CN.md @@ -154,6 +154,10 @@ opencodex 还能为 Codex 认证管理一个 **ChatGPT 账户池**。添加多 PackyCode 感谢 PackyCode 赞助本项目!PackyCode 是一家稳定、高性能的 API 中转提供商,为 Claude Code、Codex、Gemini 等提供中转服务。凭借自动故障转移、智能路由和无限并发,它让 AI 成为真正的生产力工具。通过此链接注册并开始使用!在添加提供商选择器中选择 PackyCode,或运行 ocx provider add packycode。
PackyCode 是一家稳定、高效的 API 中转服务商,提供 Claude Code、Codex、Gemini 等多种中转服务。具备自动故障转移、智能路由和无限并发等多种功能,让 AI 编程成为真正的生产力工具。点此链接注册,立即开始使用! + +TokenLab +感谢 TokenLab 赞助本项目!TokenLab 为编程代理提供一个 API 密钥即可使用主流模型,支持 Responses、Chat Completions、流式输出和工具调用。选择你的交付模式,按量付费。在添加提供商选择器中选择 TokenLab,或运行 ocx provider add tokenlab。 + diff --git a/readme/README.zh-TW.md b/readme/README.zh-TW.md index 0728a1011d2..934d611f9e7 100644 --- a/readme/README.zh-TW.md +++ b/readme/README.zh-TW.md @@ -152,6 +152,10 @@ opencodex 也能為 Codex 認證管理 **ChatGPT 帳號池**。新增多個 Chat PackyCode 感謝 PackyCode 贊助本專案!PackyCode 是穩定、高效能的 API 轉送供應商,提供 Claude Code、Codex、Gemini 等轉送服務。具備自動 failover、智慧路由與無限並行,讓 AI 成為真正的生產力工具。透過此連結註冊即可開始!在「新增供應商」選擇器選 PackyCode,或執行 ocx provider add packycode。
PackyCode 是一家稳定、高效的 API 中转服务商,提供 Claude Code、Codex、Gemini 等多种中转服务。具备自动故障转移、智能路由和无限并发等多种功能,让 AI 编程成为真正的生产力工具。点此链接注册,立即开始使用! + +TokenLab +感謝 TokenLab 贊助本專案!TokenLab 讓程式設計代理用一把 API 金鑰使用主流模型,支援 Responses、Chat Completions、串流與工具呼叫。選擇你的交付模式,按用量付費。在「新增供應商」選擇器選 TokenLab,或執行 ocx provider add tokenlab。 + diff --git a/readme/i18n-manifest.json b/readme/i18n-manifest.json index 90eb31c4f8c..bc34bfec76f 100644 --- a/readme/i18n-manifest.json +++ b/readme/i18n-manifest.json @@ -6,43 +6,43 @@ "file": "readme/README.fr.md", "label": "Français", "docsPath": "fr", - "sourceSha256": "8ae1788ef70c66f4a7cef5e368805cedcdb96925752daf7feae5a0163a830c24" + "sourceSha256": "880c3c5f3085a5d40f4c1918017e547d536afe3c6b01023ae618bda414c92e62" }, "ko": { "file": "readme/README.ko.md", "label": "한국어", "docsPath": "ko", - "sourceSha256": "8ae1788ef70c66f4a7cef5e368805cedcdb96925752daf7feae5a0163a830c24" + "sourceSha256": "880c3c5f3085a5d40f4c1918017e547d536afe3c6b01023ae618bda414c92e62" }, "zh-CN": { "file": "readme/README.zh-CN.md", "label": "简体中文", "docsPath": "zh-cn", - "sourceSha256": "8ae1788ef70c66f4a7cef5e368805cedcdb96925752daf7feae5a0163a830c24" + "sourceSha256": "880c3c5f3085a5d40f4c1918017e547d536afe3c6b01023ae618bda414c92e62" }, "zh-TW": { "file": "readme/README.zh-TW.md", "label": "繁體中文", "docsPath": "zh-tw", - "sourceSha256": "8ae1788ef70c66f4a7cef5e368805cedcdb96925752daf7feae5a0163a830c24" + "sourceSha256": "880c3c5f3085a5d40f4c1918017e547d536afe3c6b01023ae618bda414c92e62" }, "ru": { "file": "readme/README.ru.md", "label": "Русский", "docsPath": "ru", - "sourceSha256": "8ae1788ef70c66f4a7cef5e368805cedcdb96925752daf7feae5a0163a830c24" + "sourceSha256": "880c3c5f3085a5d40f4c1918017e547d536afe3c6b01023ae618bda414c92e62" }, "ja": { "file": "readme/README.ja.md", "label": "日本語", "docsPath": "ja", - "sourceSha256": "8ae1788ef70c66f4a7cef5e368805cedcdb96925752daf7feae5a0163a830c24" + "sourceSha256": "880c3c5f3085a5d40f4c1918017e547d536afe3c6b01023ae618bda414c92e62" }, "tr": { "file": "readme/README.tr.md", "label": "Türkçe", "docsPath": "tr", - "sourceSha256": "8ae1788ef70c66f4a7cef5e368805cedcdb96925752daf7feae5a0163a830c24" + "sourceSha256": "880c3c5f3085a5d40f4c1918017e547d536afe3c6b01023ae618bda414c92e62" } } } diff --git a/src/providers/registry/entries-extended.ts b/src/providers/registry/entries-extended.ts index 7740782ea4f..6976dc93a95 100644 --- a/src/providers/registry/entries-extended.ts +++ b/src/providers/registry/entries-extended.ts @@ -1146,6 +1146,9 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [ baseUrl: "https://api.tokenlab.sh/v1", authKind: "key", dashboardUrl: "https://tokenlab.sh/dashboard/api?tab=keys", + // Standard sponsor under SPONSORS.md (agreement dated 2026-09-29). Pins the row in the + // picker and adds the chip; nothing about routing, discovery or defaults changes. + sponsor: { tier: "standard", url: "https://tokenlab.sh/?utm_source=opencodex&utm_medium=readme" }, liveModels: true, preserveCustomDestination: true, defaultModel: "gpt-5.6-terra", diff --git a/structure/dashboard-and-usage.md b/structure/dashboard-and-usage.md index 3c0651f9f7a..29ddb52d626 100644 --- a/structure/dashboard-and-usage.md +++ b/structure/dashboard-and-usage.md @@ -110,9 +110,9 @@ Provider Overview consumes the existing shared `add-provider-presets` resource f presentation. `matchingWorkspacePreset` requires the configured id, adapter and normalized endpoint to match; a custom endpoint or absent sponsor metadata suppresses the introduction. `ProviderSponsor` keeps localized promotional copy and outbound HTTP(S) links separate from -operator notes. Notes remain complete and editable once in the main column; stats and current -account quota remain in the side column. This presentation does not write provider configuration -or participate in routing. +operator notes; its brand table maps each sponsor preset id (OrcaRouter, PackyCode, TokenLab) to a +name and i18n copy, and a sponsor preset without a row renders nothing. Notes stay editable once in +the main column; stats and account quota stay in the side column. Nothing here writes config or routes. Provider marks remain a name-to-asset projection in `gui/src/provider-icons.ts`. The Crusoe preset maps to the self-hosted multicolor `gui/public/provider-icons/crusoe.svg`; the gradient is rendered diff --git a/tests/providers/tokenlab-provider.test.ts b/tests/providers/tokenlab-provider.test.ts index b6ec09b3873..8fd158609e2 100644 --- a/tests/providers/tokenlab-provider.test.ts +++ b/tests/providers/tokenlab-provider.test.ts @@ -35,7 +35,9 @@ describe("TokenLab provider", () => { }); const preset = deriveProviderPresets().find(row => row.id === "tokenlab"); expect(preset).toMatchObject({ auth: "key", dashboardUrl: "https://tokenlab.sh/dashboard/api?tab=keys" }); - expect(preset?.sponsor).toBeUndefined(); + expect(preset).toMatchObject({ + sponsor: "standard", sponsorUrl: "https://tokenlab.sh/?utm_source=opencodex&utm_medium=readme", + }); expect(KEY_LOGIN_PROVIDERS.tokenlab?.defaultModel).toBe("gpt-5.6-terra"); const provider = config().providers.tokenlab!; expect(provider).not.toHaveProperty("modelDiscovery"); From 8adefd48f773b4a424eb85a5296922060fb9e4b8 Mon Sep 17 00:00:00 2001 From: JUN Date: Tue, 29 Sep 2026 22:08:15 +0900 Subject: [PATCH 2/4] fix(sponsors): pin sponsor presets in ocx init and ocx provider presets SPONSORS.md promises sponsor presets near the top of the picker in the dashboard and CLI, but only the dashboard applied it: `ocx init` listed TokenLab 84th and `ocx provider presets` printed raw registry order. Both now use the dashboard's order (Main before Standard, alphabetical by label). In `ocx init` the sponsor rows move to the start of the first run of their own kind, so they stay under the right heading and every other row keeps its place; `ocx provider presets` lists sponsor rows first. The derivation and the /api/provider-presets payload are unchanged. --- SPONSORS.md | 4 +- src/cli/init.ts | 3 +- src/cli/provider-runtime.ts | 11 ++++- src/providers/sponsor-order.ts | 55 +++++++++++++++++++++++++ tests/providers/sponsor-presets.test.ts | 29 +++++++++++++ 5 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 src/providers/sponsor-order.ts diff --git a/SPONSORS.md b/SPONSORS.md index 5eed39bd66e..97506d9ce9a 100644 --- a/SPONSORS.md +++ b/SPONSORS.md @@ -47,8 +47,8 @@ sponsor receives: policy. A second-language blurb (for example Chinese) may run alongside the English one. - A built-in provider preset (`ocx provider add `) shipped in a public npm release, listed near the top of the provider picker in the dashboard and CLI and marked as a sponsor - there. The dashboard picker lists sponsor rows Main before Standard, then alphabetically by - label, an order no sponsor can buy. + there. The dashboard picker, `ocx init` and `ocx provider presets` list sponsor rows Main + before Standard, then alphabetically by label, an order no sponsor can buy. - A detailed entry on the [providers page](https://opencodex.me/guides/providers/) of the docs site. - Maintenance: if a release breaks the preset or its adapter, the maintainer fixes it; issues diff --git a/src/cli/init.ts b/src/cli/init.ts index e53b203415a..e3ae5155358 100644 --- a/src/cli/init.ts +++ b/src/cli/init.ts @@ -8,6 +8,7 @@ import { InitialConfigPublicationError } from "../config/initialize"; import { redactUserPath } from "../lib/redact"; import { enrichProviderFromCatalog } from "../oauth/key-providers"; import { deriveInitProviders } from "../providers/derive"; +import { pinSponsorsWithinKind } from "../providers/sponsor-order"; import type { OcxConfig, OcxProviderConfig } from "../types"; class InitCancelledError extends Error { @@ -126,7 +127,7 @@ export async function runInit(): Promise { try { console.log("\n🔧 opencodex (ocx) setup\n"); - const providers = buildInitProviders(); + const providers = pinSponsorsWithinKind(buildInitProviders()); printMenu(providers); const choice = await prompt.ask("\nSelect default provider (number): "); diff --git a/src/cli/provider-runtime.ts b/src/cli/provider-runtime.ts index ff3d9f96d2c..3b20703eb60 100644 --- a/src/cli/provider-runtime.ts +++ b/src/cli/provider-runtime.ts @@ -13,6 +13,7 @@ import { type RuntimeApiDeps, } from "./runtime-api"; import { providerQuotaLine } from "./account-extended"; +import { pinSponsorRows } from "../providers/sponsor-order"; import type { ProviderQuotaReportDto } from "./account-api"; interface ProviderQuotasDto { @@ -233,7 +234,15 @@ async function presets(argv: string[], deps: RuntimeApiDeps): Promise { rejectArgs(args, USAGE); const result = await runtimeRequest<{ providers?: unknown[] } | unknown[]>("/api/provider-presets", {}, deps); const rows = Array.isArray(result) ? result : result.providers ?? []; - printData(result, wantsJson, rows.map(row => { + const pinned = pinSponsorRows( + rows, + row => { + const tier = (row as Record)?.sponsor; + return tier === "main" || tier === "standard" ? tier : undefined; + }, + row => String((row as Record)?.label ?? (row as Record)?.id ?? ""), + ); + printData(result, wantsJson, pinned.map(row => { const record = row as Record; const sponsor = record.sponsor ? ` (sponsor: ${String(record.sponsor)})` : ""; return `${String(record.id ?? record.name ?? "?")} ${String(record.label ?? record.adapter ?? "")}${sponsor}`.trimEnd(); diff --git a/src/providers/sponsor-order.ts b/src/providers/sponsor-order.ts new file mode 100644 index 00000000000..53f42fcee71 --- /dev/null +++ b/src/providers/sponsor-order.ts @@ -0,0 +1,55 @@ +import { PROVIDER_REGISTRY } from "./registry"; + +export type SponsorTier = "main" | "standard"; + +const SPONSOR_RANK: Record = { main: 0, standard: 1 }; + +/** + * SPONSORS.md promises sponsor presets sit near the top of the picker "in the dashboard and CLI". + * The dashboard applies that in gui/src/components/provider-catalog/provider-presets.ts + * (`pinSponsors`); this is the same order for the CLI surfaces: sponsor rows first, Main before + * Standard, alphabetical by label within a tier, then every other row in the caller's order. + * Alphabetical among sponsors is deliberate: it is the one order no sponsor can buy. + */ +export function pinSponsorRows( + rows: readonly T[], + tierOf: (row: T) => SponsorTier | undefined, + labelOf: (row: T) => string, +): T[] { + const sponsors = rows.filter(row => tierOf(row) !== undefined); + if (sponsors.length === 0) return [...rows]; + sponsors.sort((a, b) => + SPONSOR_RANK[tierOf(a)!] - SPONSOR_RANK[tierOf(b)!] + || labelOf(a).localeCompare(labelOf(b), undefined, { sensitivity: "base" })); + return [...sponsors, ...rows.filter(row => tierOf(row) === undefined)]; +} + +/** Registry sponsor tier by preset id; the registry field is the only thing that marks a sponsor. */ +export function registrySponsorTier(id: string): SponsorTier | undefined { + return PROVIDER_REGISTRY.find(entry => entry.id === id)?.sponsor?.tier; +} + +/** + * The `ocx init` menu prints a heading whenever the provider kind changes, and registry order + * interleaves kinds, so the same heading appears more than once. Sponsor rows move, in pinned + * order, to the start of the first run of their own kind: they stay under the right heading, sit + * near the top of the menu, and no new heading is introduced. Every other row keeps its order. + */ +export function pinSponsorsWithinKind(rows: readonly T[]): T[] { + const tierOf = (row: T) => registrySponsorTier(row.id); + const sponsors = pinSponsorRows(rows.filter(row => tierOf(row) !== undefined), tierOf, row => row.label); + if (sponsors.length === 0) return [...rows]; + const out: T[] = []; + const placedKinds = new Set(); + for (const row of rows) { + if (tierOf(row) !== undefined) continue; + if (!placedKinds.has(row.kind)) { + placedKinds.add(row.kind); + out.push(...sponsors.filter(sponsor => sponsor.kind === row.kind)); + } + out.push(row); + } + // A sponsor whose kind has no other row still appears, at the end, rather than vanishing. + out.push(...sponsors.filter(sponsor => !placedKinds.has(sponsor.kind))); + return out; +} diff --git a/tests/providers/sponsor-presets.test.ts b/tests/providers/sponsor-presets.test.ts index c7e28e9e16a..e5a108601b0 100644 --- a/tests/providers/sponsor-presets.test.ts +++ b/tests/providers/sponsor-presets.test.ts @@ -1,6 +1,8 @@ import { describe, expect, test } from "bun:test"; +import { buildInitProviders } from "../../src/cli/init"; import { deriveProviderPresets } from "../../src/providers/derive"; import { PROVIDER_REGISTRY } from "../../src/providers/registry"; +import { pinSponsorRows, pinSponsorsWithinKind } from "../../src/providers/sponsor-order"; /** * The registry `sponsor` field is the only thing that marks a paid sponsor, and SPONSORS.md @@ -36,4 +38,31 @@ describe("sponsor presets", () => { const deduped = registryOrder.filter(id => (seen.has(id) ? false : (seen.add(id), true))); expect(ids).toEqual(deduped); }); + + test("ocx init moves sponsors to the start of the first run of their kind, alphabetical by label", () => { + const rows = pinSponsorsWithinKind(buildInitProviders()); + const sponsorIds = PROVIDER_REGISTRY.filter(entry => entry.sponsor).map(entry => entry.id); + expect(sponsorIds.length).toBeGreaterThan(0); + const firstKey = rows.findIndex(row => row.kind === "key"); + const leading = rows.slice(firstKey, firstKey + sponsorIds.length); + expect(leading.map(row => row.id).sort()).toEqual([...sponsorIds].sort()); + const labels = leading.map(row => row.label); + expect(labels).toEqual([...labels].sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }))); + // Same rows; non-sponsor rows keep their order, so the heading sequence is unchanged. + expect(rows.map(row => row.id).sort()).toEqual(buildInitProviders().map(row => row.id).sort()); + const rest = (list: { id: string }[]) => list.map(row => row.id).filter(id => !sponsorIds.includes(id)); + expect(rest(rows)).toEqual(rest(buildInitProviders())); + const kinds = (list: { kind: string }[]) => list.map(row => row.kind).filter((kind, i, all) => kind !== all[i - 1]); + expect(kinds(rows)).toEqual(kinds(buildInitProviders())); + }); + + test("pinSponsorRows orders Main before Standard and keeps the rest in caller order", () => { + const rows = [ + { id: "a", label: "Zed" }, { id: "b", label: "beta", tier: "standard" as const }, + { id: "c", label: "Mid" }, { id: "d", label: "Alpha", tier: "standard" as const }, + { id: "e", label: "Omega", tier: "main" as const }, + ]; + const pinned = pinSponsorRows(rows, row => row.tier, row => row.label).map(row => row.id); + expect(pinned).toEqual(["e", "d", "b", "a", "c"]); + }); }); From 21cddd35c99dbe0d276b6af7a4ad7431b8e3248e Mon Sep 17 00:00:00 2001 From: JUN Date: Tue, 29 Sep 2026 22:29:07 +0900 Subject: [PATCH 3/4] fix(sponsors): keep a sponsor-first run in place in the init menu pinSponsorsWithinKind recorded a kind only at its first non-sponsor row, so a run that opened with a sponsor moved that sponsor to a later run of the same kind (or to the end when its kind had no other row), changing the heading sequence. The kind is now recorded at its first row of any kind. Reported by CodeRabbit on #6240. --- src/providers/sponsor-order.ts | 13 +++++++------ tests/providers/sponsor-presets.test.ts | 13 +++++++++++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/providers/sponsor-order.ts b/src/providers/sponsor-order.ts index 53f42fcee71..7dff406fd4e 100644 --- a/src/providers/sponsor-order.ts +++ b/src/providers/sponsor-order.ts @@ -35,21 +35,22 @@ export function registrySponsorTier(id: string): SponsorTier | undefined { * order, to the start of the first run of their own kind: they stay under the right heading, sit * near the top of the menu, and no new heading is introduced. Every other row keeps its order. */ -export function pinSponsorsWithinKind(rows: readonly T[]): T[] { - const tierOf = (row: T) => registrySponsorTier(row.id); +export function pinSponsorsWithinKind( + rows: readonly T[], + tierOf: (row: T) => SponsorTier | undefined = row => registrySponsorTier(row.id), +): T[] { const sponsors = pinSponsorRows(rows.filter(row => tierOf(row) !== undefined), tierOf, row => row.label); if (sponsors.length === 0) return [...rows]; const out: T[] = []; const placedKinds = new Set(); for (const row of rows) { - if (tierOf(row) !== undefined) continue; + // Record the kind at its first row, sponsor or not, so a run that opens with a sponsor + // keeps its place instead of the sponsor moving to a later run of the same kind. if (!placedKinds.has(row.kind)) { placedKinds.add(row.kind); out.push(...sponsors.filter(sponsor => sponsor.kind === row.kind)); } - out.push(row); + if (tierOf(row) === undefined) out.push(row); } - // A sponsor whose kind has no other row still appears, at the end, rather than vanishing. - out.push(...sponsors.filter(sponsor => !placedKinds.has(sponsor.kind))); return out; } diff --git a/tests/providers/sponsor-presets.test.ts b/tests/providers/sponsor-presets.test.ts index e5a108601b0..740e52a36be 100644 --- a/tests/providers/sponsor-presets.test.ts +++ b/tests/providers/sponsor-presets.test.ts @@ -65,4 +65,17 @@ describe("sponsor presets", () => { const pinned = pinSponsorRows(rows, row => row.tier, row => row.label).map(row => row.id); expect(pinned).toEqual(["e", "d", "b", "a", "c"]); }); + + test("a run that opens with a sponsor keeps its place in the kind sequence", () => { + type Row = { id: string; label: string; kind: string; tier?: "main" | "standard" }; + const rows: Row[] = [ + { id: "s1", label: "Sponsor B", kind: "key", tier: "standard" }, + { id: "o1", label: "OAuth", kind: "oauth" }, + { id: "k1", label: "Key", kind: "key" }, + { id: "s2", label: "Sponsor A", kind: "key", tier: "standard" }, + { id: "s3", label: "Only", kind: "local", tier: "main" }, + ]; + const pinned = pinSponsorsWithinKind(rows, row => row.tier).map(row => row.id); + expect(pinned).toEqual(["s2", "s1", "o1", "k1", "s3"]); + }); }); From 2b9295fea62e2396675e0ea05ab25283a06423f1 Mon Sep 17 00:00:00 2001 From: JUN Date: Wed, 30 Sep 2026 00:33:05 +0900 Subject: [PATCH 4/4] docs(sponsors): use TokenLab's final blurb and referral link TokenLab sent its final sponsor copy on 2026-09-29: an English blurb that also names Anthropic Messages, Gemini's native API, an MCP server and agent Skills, a Chinese blurb, and https://tokenlab.sh/r/OPENCODEX as the README and picker link in place of the homepage. The README row now carries both blurbs verbatim (the Chinese one as the second-language line, as for PackyCode), the translated rows follow, and the registry sponsor URL, providers guide and tests use the referral link. --- README.md | 4 ++-- docs-site/src/content/docs/guides/providers.md | 2 +- gui/tests/provider-sponsor-overview.test.tsx | 4 ++-- readme/README.fr.md | 4 ++-- readme/README.ja.md | 4 ++-- readme/README.ko.md | 4 ++-- readme/README.ru.md | 4 ++-- readme/README.tr.md | 4 ++-- readme/README.zh-CN.md | 4 ++-- readme/README.zh-TW.md | 4 ++-- readme/i18n-manifest.json | 14 +++++++------- src/providers/registry/entries-extended.ts | 2 +- tests/providers/tokenlab-provider.test.ts | 2 +- 13 files changed, 28 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 65acb9547e4..907a481db91 100644 --- a/README.md +++ b/README.md @@ -158,8 +158,8 @@ See [SPONSORS.md](./SPONSORS.md). Thanks to PackyCode for sponsoring this project! PackyCode is a stable, high-performance API relay provider, offering relay services for Claude Code, Codex, Gemini, and more. With automatic failover, smart routing, and unlimited concurrency, it turns AI into a real productivity tool. Register via this link and get started! Pick PackyCode in the Add provider picker or run ocx provider add packycode.
PackyCode 是一家稳定、高效的 API 中转服务商,提供 Claude Code、Codex、Gemini 等多种中转服务。具备自动故障转移、智能路由和无限并发等多种功能,让 AI 编程成为真正的生产力工具。点此链接注册,立即开始使用! -TokenLab -Thanks to TokenLab for sponsoring this project! TokenLab gives coding agents one API key for leading models, with Responses, Chat Completions, streaming, and tool calling. Choose your delivery mode and pay as you go. Pick TokenLab in the Add provider picker or run ocx provider add tokenlab. +TokenLab +Thanks to TokenLab for sponsoring this project! TokenLab gives coding agents one API key for leading models, supporting OpenAI Responses and Chat Completions, Anthropic Messages, and Gemini's native API formats, with streaming and tool calling. It also provides an MCP server and agent Skills for easy integration. Choose your delivery mode and pay as you go. Pick TokenLab in the Add provider picker or run ocx provider add tokenlab.
TokenLab 为编程智能体提供统一的多模型 API,一枚 API Key 即可接入主流模型,支持 OpenAI Responses、Chat Completions、Anthropic Messages 和 Gemini 原生 API 格式,以及流式输出和工具调用。同时提供 MCP 服务器和 Agent Skills,方便接入现有工作流;交付模式可选,按量付费。 diff --git a/docs-site/src/content/docs/guides/providers.md b/docs-site/src/content/docs/guides/providers.md index ca0430e7f92..5bbf4735c66 100644 --- a/docs-site/src/content/docs/guides/providers.md +++ b/docs-site/src/content/docs/guides/providers.md @@ -554,7 +554,7 @@ region-pinned EU routes, is at [opper.ai/models](https://opper.ai/models). Opper | …and more | opencode zen, Vercel AI Gateway, Venice, NanoGPT, Synthetic, Qianfan, Alibaba, Parallel, ZenMux, LiteLLM | **TokenLab** ([sponsor](https://github.com/lidge-jun/opencodex/blob/main/SPONSORS.md)) is an -OpenAI-compatible API gateway at [tokenlab.sh](https://tokenlab.sh/?utm_source=opencodex&utm_medium=readme), +OpenAI-compatible API gateway at [tokenlab.sh](https://tokenlab.sh/r/OPENCODEX), operated by TOKENLAB AI INC. Create a workspace [API key](https://tokenlab.sh/dashboard/api?tab=keys), then run `ocx provider add tokenlab` or select **TokenLab** in the dashboard's **Add provider** picker. diff --git a/gui/tests/provider-sponsor-overview.test.tsx b/gui/tests/provider-sponsor-overview.test.tsx index efb5c0b754f..e535e9e115f 100644 --- a/gui/tests/provider-sponsor-overview.test.tsx +++ b/gui/tests/provider-sponsor-overview.test.tsx @@ -21,7 +21,7 @@ const packy: CatalogPreset = { const tokenlab: CatalogPreset = { id: "tokenlab", label: "TokenLab", adapter: "openai-chat", auth: "key", baseUrl: "https://api.tokenlab.sh/v1", sponsor: "standard", - sponsorUrl: "https://tokenlab.sh/?utm_source=opencodex&utm_medium=readme", + sponsorUrl: "https://tokenlab.sh/r/OPENCODEX", dashboardUrl: "https://tokenlab.sh/dashboard/api?tab=keys", }; const configured = (preset: CatalogPreset): WorkspaceItem => ({ @@ -71,7 +71,7 @@ test("TokenLab renders its own copy with separate sponsor and API-key links", () expect(html).toContain("TokenLab"); expect(html).toContain("One API key for leading models"); expect(html).not.toContain("A model for every prompt"); - expect(html).toContain('href="https://tokenlab.sh/?utm_source=opencodex&utm_medium=readme"'); + expect(html).toContain('href="https://tokenlab.sh/r/OPENCODEX"'); expect(html).toContain('href="https://tokenlab.sh/dashboard/api?tab=keys"'); expect(html.match(/Merci à PackyCode pour son soutien à ce projet ! PackyCode est un fournisseur de relais API stable et performant, qui propose des services de relais pour Claude Code, Codex, Gemini et d'autres. Grâce au basculement automatique, au routage intelligent et à une concurrence illimitée, il fait de l'IA un véritable outil de productivité. Inscrivez-vous via ce lien et commencez ! Choisissez PackyCode dans le sélecteur Add provider ou exécutez ocx provider add packycode.
PackyCode 是一家稳定、高效的 API 中转服务商,提供 Claude Code、Codex、Gemini 等多种中转服务。具备自动故障转移、智能路由和无限并发等多种功能,让 AI 编程成为真正的生产力工具。点此链接注册,立即开始使用! -TokenLab -Merci à TokenLab pour son soutien à ce projet ! TokenLab donne aux agents de code une seule clé API pour les principaux modèles, avec Responses, Chat Completions, le streaming et l'appel d'outils. Choisissez votre mode de livraison et payez à l'usage. Choisissez TokenLab dans le sélecteur Add provider ou exécutez ocx provider add tokenlab. +TokenLab +Merci à TokenLab pour son soutien à ce projet ! TokenLab donne aux agents de code une seule clé API pour les principaux modèles, avec les formats OpenAI Responses et Chat Completions, Anthropic Messages et l'API native de Gemini, streaming et appel d'outils compris. Il fournit aussi un serveur MCP et des Skills pour agents afin de faciliter l'intégration. Choisissez votre mode de livraison et payez à l'usage. Choisissez TokenLab dans le sélecteur Add provider ou exécutez ocx provider add tokenlab.
TokenLab 为编程智能体提供统一的多模型 API,一枚 API Key 即可接入主流模型,支持 OpenAI Responses、Chat Completions、Anthropic Messages 和 Gemini 原生 API 格式,以及流式输出和工具调用。同时提供 MCP 服务器和 Agent Skills,方便接入现有工作流;交付模式可选,按量付费。 diff --git a/readme/README.ja.md b/readme/README.ja.md index 5e41ec22db8..fafe9da489a 100644 --- a/readme/README.ja.md +++ b/readme/README.ja.md @@ -157,8 +157,8 @@ failover、アカウントの除外、affinity の失効、401/403 や 429 か このプロジェクトを支援してくださる PackyCode に感謝します。PackyCode は安定した高性能の API リレープロバイダーで、Claude Code、Codex、Gemini などのリレーを提供しています。自動 failover、スマートルーティング、無制限の同時実行によって、AI を実際の生産性ツールに変えます。このリンクから登録してすぐに始めてください。Add provider ピッカーで PackyCode を選ぶか ocx provider add packycode を実行してください。
PackyCode 是一家稳定、高效的 API 中转服务商,提供 Claude Code、Codex、Gemini 等多种中转服务。具备自动故障转移、智能路由和无限并发等多种功能,让 AI 编程成为真正的生产力工具。点此链接注册,立即开始使用! -TokenLab -このプロジェクトを支援してくださる TokenLab に感謝します。TokenLab はコーディングエージェントに主要モデル向けの API キーを 1 つだけ提供し、Responses、Chat Completions、ストリーミング、ツール呼び出しに対応しています。配信モードを選び、使った分だけ支払えます。Add provider ピッカーで TokenLab を選ぶか ocx provider add tokenlab を実行してください。 +TokenLab +このプロジェクトを支援してくださる TokenLab に感謝します。TokenLab はコーディングエージェントに主要モデル向けの API キーを 1 つだけ提供し、OpenAI Responses と Chat Completions、Anthropic Messages、Gemini ネイティブ API の各形式に、ストリーミングとツール呼び出し込みで対応しています。MCP サーバーとエージェント Skills も用意されており、簡単に組み込めます。配信モードを選び、使った分だけ支払えます。Add provider ピッカーで TokenLab を選ぶか ocx provider add tokenlab を実行してください。
TokenLab 为编程智能体提供统一的多模型 API,一枚 API Key 即可接入主流模型,支持 OpenAI Responses、Chat Completions、Anthropic Messages 和 Gemini 原生 API 格式,以及流式输出和工具调用。同时提供 MCP 服务器和 Agent Skills,方便接入现有工作流;交付模式可选,按量付费。 diff --git a/readme/README.ko.md b/readme/README.ko.md index 2dec3243405..26eee185dce 100644 --- a/readme/README.ko.md +++ b/readme/README.ko.md @@ -154,8 +154,8 @@ Codex Desktop 로그인처럼 다른 계정이 소진된 뒤에만 쓰고 싶은 PackyCode의 후원에 감사합니다. PackyCode는 안정적인 고성능 API 릴레이 프로바이더로, Claude Code, Codex, Gemini 등의 릴레이를 제공합니다. 자동 failover, 스마트 라우팅, 무제한 동시성으로 AI를 실제 생산성 도구로 만듭니다. 이 링크로 등록하고 바로 시작하세요. Add provider 선택기에서 PackyCode를 고르거나 ocx provider add packycode를 실행하세요.
PackyCode 是一家稳定、高效的 API 中转服务商,提供 Claude Code、Codex、Gemini 等多种中转服务。具备自动故障转移、智能路由和无限并发等多种功能,让 AI 编程成为真正的生产力工具。点此链接注册,立即开始使用! -TokenLab -TokenLab의 후원에 감사합니다. TokenLab은 코딩 에이전트에게 주요 모델용 API 키 하나를 제공하며, Responses, Chat Completions, 스트리밍, 도구 호출을 지원합니다. 전달 모드를 고르고 쓴 만큼 결제하세요. Add provider 선택기에서 TokenLab을 고르거나 ocx provider add tokenlab을 실행하세요. +TokenLab +TokenLab의 후원에 감사합니다. TokenLab은 코딩 에이전트에게 주요 모델용 API 키 하나를 제공하며, OpenAI Responses와 Chat Completions, Anthropic Messages, Gemini 네이티브 API 형식을 스트리밍과 도구 호출까지 지원합니다. MCP 서버와 에이전트 Skills도 제공해 쉽게 연동할 수 있습니다. 전달 모드를 고르고 쓴 만큼 결제하세요. Add provider 선택기에서 TokenLab을 고르거나 ocx provider add tokenlab을 실행하세요.
TokenLab 为编程智能体提供统一的多模型 API,一枚 API Key 即可接入主流模型,支持 OpenAI Responses、Chat Completions、Anthropic Messages 和 Gemini 原生 API 格式,以及流式输出和工具调用。同时提供 MCP 服务器和 Agent Skills,方便接入现有工作流;交付模式可选,按量付费。 diff --git a/readme/README.ru.md b/readme/README.ru.md index 3f5558b66a8..6732e0f3126 100644 --- a/readme/README.ru.md +++ b/readme/README.ru.md @@ -165,8 +165,8 @@ opencodex также умеет управлять **пулом аккаунто Благодарим PackyCode за спонсорскую поддержку проекта! PackyCode — стабильный высокопроизводительный API-релей, предоставляющий релей-сервисы для Claude Code, Codex, Gemini и других. Автоматический failover, умная маршрутизация и неограниченная конкурентность превращают AI в настоящий инструмент продуктивности. Зарегистрируйтесь по этой ссылке и начните работу! Выберите PackyCode в селекторе Add provider или выполните ocx provider add packycode.
PackyCode 是一家稳定、高效的 API 中转服务商,提供 Claude Code、Codex、Gemini 等多种中转服务。具备自动故障转移、智能路由和无限并发等多种功能,让 AI 编程成为真正的生产力工具。点此链接注册,立即开始使用! -TokenLab -Благодарим TokenLab за спонсорскую поддержку проекта! TokenLab даёт агентам для программирования один API-ключ для ведущих моделей с поддержкой Responses, Chat Completions, стриминга и вызова инструментов. Выберите режим доставки и платите по мере использования. Выберите TokenLab в селекторе Add provider или выполните ocx provider add tokenlab. +TokenLab +Благодарим TokenLab за спонсорскую поддержку проекта! TokenLab даёт агентам для программирования один API-ключ для ведущих моделей с поддержкой форматов OpenAI Responses и Chat Completions, Anthropic Messages и нативного API Gemini, включая стриминг и вызов инструментов. Также доступны MCP-сервер и Skills для агентов, чтобы упростить интеграцию. Выберите режим доставки и платите по мере использования. Выберите TokenLab в селекторе Add provider или выполните ocx provider add tokenlab.
TokenLab 为编程智能体提供统一的多模型 API,一枚 API Key 即可接入主流模型,支持 OpenAI Responses、Chat Completions、Anthropic Messages 和 Gemini 原生 API 格式,以及流式输出和工具调用。同时提供 MCP 服务器和 Agent Skills,方便接入现有工作流;交付模式可选,按量付费。 diff --git a/readme/README.tr.md b/readme/README.tr.md index a16fa0ce936..6e99c74da04 100644 --- a/readme/README.tr.md +++ b/readme/README.tr.md @@ -161,8 +161,8 @@ Her yukarı akış protokol değişiminde opencodex'in bakımını sürdürebilm Bu projeye sponsor olduğu için PackyCode'a teşekkürler! PackyCode, Claude Code, Codex, Gemini ve daha fazlası için aktarma hizmeti sunan istikrarlı ve yüksek başarımlı bir API aktarma sağlayıcısıdır. Otomatik failover, akıllı yönlendirme ve sınırsız eşzamanlılıkla yapay zekâyı gerçek bir üretkenlik aracına dönüştürür. Bu bağlantıdan kaydolun ve hemen başlayın! Add provider seçicisinden PackyCode seçin ya da ocx provider add packycode çalıştırın.
PackyCode 是一家稳定、高效的 API 中转服务商,提供 Claude Code、Codex、Gemini 等多种中转服务。具备自动故障转移、智能路由和无限并发等多种功能,让 AI 编程成为真正的生产力工具。点此链接注册,立即开始使用! -TokenLab -Bu projeye sponsor olduğu için TokenLab'a teşekkürler! TokenLab, kodlama ajanlarına önde gelen modeller için tek bir API anahtarı sunar; Responses, Chat Completions, akış ve araç çağırma desteklenir. Teslimat modunuzu seçin, kullandıkça ödeyin. Add provider seçicisinden TokenLab seçin ya da ocx provider add tokenlab çalıştırın. +TokenLab +Bu projeye sponsor olduğu için TokenLab'a teşekkürler! TokenLab, kodlama ajanlarına önde gelen modeller için tek bir API anahtarı sunar; OpenAI Responses ve Chat Completions, Anthropic Messages ve Gemini'nin yerel API biçimleri, akış ve araç çağırma ile desteklenir. Kolay entegrasyon için bir MCP sunucusu ve ajan Skills'i de sağlar. Teslimat modunuzu seçin, kullandıkça ödeyin. Add provider seçicisinden TokenLab seçin ya da ocx provider add tokenlab çalıştırın.
TokenLab 为编程智能体提供统一的多模型 API,一枚 API Key 即可接入主流模型,支持 OpenAI Responses、Chat Completions、Anthropic Messages 和 Gemini 原生 API 格式,以及流式输出和工具调用。同时提供 MCP 服务器和 Agent Skills,方便接入现有工作流;交付模式可选,按量付费。 diff --git a/readme/README.zh-CN.md b/readme/README.zh-CN.md index f4765a81273..f6e4131498e 100644 --- a/readme/README.zh-CN.md +++ b/readme/README.zh-CN.md @@ -155,8 +155,8 @@ opencodex 还能为 Codex 认证管理一个 **ChatGPT 账户池**。添加多 感谢 PackyCode 赞助本项目!PackyCode 是一家稳定、高性能的 API 中转提供商,为 Claude Code、Codex、Gemini 等提供中转服务。凭借自动故障转移、智能路由和无限并发,它让 AI 成为真正的生产力工具。通过此链接注册并开始使用!在添加提供商选择器中选择 PackyCode,或运行 ocx provider add packycode。
PackyCode 是一家稳定、高效的 API 中转服务商,提供 Claude Code、Codex、Gemini 等多种中转服务。具备自动故障转移、智能路由和无限并发等多种功能,让 AI 编程成为真正的生产力工具。点此链接注册,立即开始使用! -TokenLab -感谢 TokenLab 赞助本项目!TokenLab 为编程代理提供一个 API 密钥即可使用主流模型,支持 Responses、Chat Completions、流式输出和工具调用。选择你的交付模式,按量付费。在添加提供商选择器中选择 TokenLab,或运行 ocx provider add tokenlab。 +TokenLab +感谢 TokenLab 赞助本项目!TokenLab 为编程智能体提供统一的多模型 API,一枚 API Key 即可接入主流模型,支持 OpenAI Responses、Chat Completions、Anthropic Messages 和 Gemini 原生 API 格式,以及流式输出和工具调用。同时提供 MCP 服务器和 Agent Skills,方便接入现有工作流;交付模式可选,按量付费。在添加提供商选择器中选择 TokenLab,或运行 ocx provider add tokenlab。 diff --git a/readme/README.zh-TW.md b/readme/README.zh-TW.md index 934d611f9e7..b53a5d0ed19 100644 --- a/readme/README.zh-TW.md +++ b/readme/README.zh-TW.md @@ -153,8 +153,8 @@ opencodex 也能為 Codex 認證管理 **ChatGPT 帳號池**。新增多個 Chat 感謝 PackyCode 贊助本專案!PackyCode 是穩定、高效能的 API 轉送供應商,提供 Claude Code、Codex、Gemini 等轉送服務。具備自動 failover、智慧路由與無限並行,讓 AI 成為真正的生產力工具。透過此連結註冊即可開始!在「新增供應商」選擇器選 PackyCode,或執行 ocx provider add packycode。
PackyCode 是一家稳定、高效的 API 中转服务商,提供 Claude Code、Codex、Gemini 等多种中转服务。具备自动故障转移、智能路由和无限并发等多种功能,让 AI 编程成为真正的生产力工具。点此链接注册,立即开始使用! -TokenLab -感謝 TokenLab 贊助本專案!TokenLab 讓程式設計代理用一把 API 金鑰使用主流模型,支援 Responses、Chat Completions、串流與工具呼叫。選擇你的交付模式,按用量付費。在「新增供應商」選擇器選 TokenLab,或執行 ocx provider add tokenlab。 +TokenLab +感謝 TokenLab 贊助本專案!TokenLab 為程式設計智慧體提供統一的多模型 API,一把 API Key 即可接入主流模型,支援 OpenAI Responses、Chat Completions、Anthropic Messages 和 Gemini 原生 API 格式,以及串流輸出和工具呼叫。同時提供 MCP 伺服器和 Agent Skills,方便接入現有工作流程;交付模式可選,按量付費。在「新增供應商」選擇器選 TokenLab,或執行 ocx provider add tokenlab。 diff --git a/readme/i18n-manifest.json b/readme/i18n-manifest.json index bc34bfec76f..2a4b2803aee 100644 --- a/readme/i18n-manifest.json +++ b/readme/i18n-manifest.json @@ -6,43 +6,43 @@ "file": "readme/README.fr.md", "label": "Français", "docsPath": "fr", - "sourceSha256": "880c3c5f3085a5d40f4c1918017e547d536afe3c6b01023ae618bda414c92e62" + "sourceSha256": "775eef12d4c0efec79cd4951e74f9e7ceaf244de815360fd3f78729f2420510d" }, "ko": { "file": "readme/README.ko.md", "label": "한국어", "docsPath": "ko", - "sourceSha256": "880c3c5f3085a5d40f4c1918017e547d536afe3c6b01023ae618bda414c92e62" + "sourceSha256": "775eef12d4c0efec79cd4951e74f9e7ceaf244de815360fd3f78729f2420510d" }, "zh-CN": { "file": "readme/README.zh-CN.md", "label": "简体中文", "docsPath": "zh-cn", - "sourceSha256": "880c3c5f3085a5d40f4c1918017e547d536afe3c6b01023ae618bda414c92e62" + "sourceSha256": "775eef12d4c0efec79cd4951e74f9e7ceaf244de815360fd3f78729f2420510d" }, "zh-TW": { "file": "readme/README.zh-TW.md", "label": "繁體中文", "docsPath": "zh-tw", - "sourceSha256": "880c3c5f3085a5d40f4c1918017e547d536afe3c6b01023ae618bda414c92e62" + "sourceSha256": "775eef12d4c0efec79cd4951e74f9e7ceaf244de815360fd3f78729f2420510d" }, "ru": { "file": "readme/README.ru.md", "label": "Русский", "docsPath": "ru", - "sourceSha256": "880c3c5f3085a5d40f4c1918017e547d536afe3c6b01023ae618bda414c92e62" + "sourceSha256": "775eef12d4c0efec79cd4951e74f9e7ceaf244de815360fd3f78729f2420510d" }, "ja": { "file": "readme/README.ja.md", "label": "日本語", "docsPath": "ja", - "sourceSha256": "880c3c5f3085a5d40f4c1918017e547d536afe3c6b01023ae618bda414c92e62" + "sourceSha256": "775eef12d4c0efec79cd4951e74f9e7ceaf244de815360fd3f78729f2420510d" }, "tr": { "file": "readme/README.tr.md", "label": "Türkçe", "docsPath": "tr", - "sourceSha256": "880c3c5f3085a5d40f4c1918017e547d536afe3c6b01023ae618bda414c92e62" + "sourceSha256": "775eef12d4c0efec79cd4951e74f9e7ceaf244de815360fd3f78729f2420510d" } } } diff --git a/src/providers/registry/entries-extended.ts b/src/providers/registry/entries-extended.ts index 6976dc93a95..b72eb462c33 100644 --- a/src/providers/registry/entries-extended.ts +++ b/src/providers/registry/entries-extended.ts @@ -1148,7 +1148,7 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [ dashboardUrl: "https://tokenlab.sh/dashboard/api?tab=keys", // Standard sponsor under SPONSORS.md (agreement dated 2026-09-29). Pins the row in the // picker and adds the chip; nothing about routing, discovery or defaults changes. - sponsor: { tier: "standard", url: "https://tokenlab.sh/?utm_source=opencodex&utm_medium=readme" }, + sponsor: { tier: "standard", url: "https://tokenlab.sh/r/OPENCODEX" }, liveModels: true, preserveCustomDestination: true, defaultModel: "gpt-5.6-terra", diff --git a/tests/providers/tokenlab-provider.test.ts b/tests/providers/tokenlab-provider.test.ts index 8fd158609e2..1d6ef8b76e7 100644 --- a/tests/providers/tokenlab-provider.test.ts +++ b/tests/providers/tokenlab-provider.test.ts @@ -36,7 +36,7 @@ describe("TokenLab provider", () => { const preset = deriveProviderPresets().find(row => row.id === "tokenlab"); expect(preset).toMatchObject({ auth: "key", dashboardUrl: "https://tokenlab.sh/dashboard/api?tab=keys" }); expect(preset).toMatchObject({ - sponsor: "standard", sponsorUrl: "https://tokenlab.sh/?utm_source=opencodex&utm_medium=readme", + sponsor: "standard", sponsorUrl: "https://tokenlab.sh/r/OPENCODEX", }); expect(KEY_LOGIN_PROVIDERS.tokenlab?.defaultModel).toBe("gpt-5.6-terra"); const provider = config().providers.tokenlab!;