feat(kenari): add Kenari as a first-class provider#793
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
🚧 Files skipped from review as they are similar to previous changes (5)
📝 WalkthroughWalkthroughKenari is added as a first-class provider across shared types, backend handling, router-model fetching, settings UI wiring, validation, and localized strings. The PR also adds provider-specific model resolution, fetchers, and tests. ChangesKenari Provider Integration
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/api/providers/fetchers/kenari.ts (1)
7-7: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winBase URL string duplicated with the API handler.
KENARI_BASE_URLhere and the hardcoded"https://kenari.id/v1"literal insrc/api/providers/kenari.ts(constructorbaseURL) are two independent sources of truth. If Kenari's endpoint changes, only one may get updated.♻️ Proposed fix: export and reuse the constant
-const KENARI_BASE_URL = "https://kenari.id/v1" +export const KENARI_BASE_URL = "https://kenari.id/v1"Then in
src/api/providers/kenari.ts:- baseURL: "https://kenari.id/v1", + baseURL: KENARI_BASE_URL,🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/api/providers/fetchers/kenari.ts` at line 7, The Kenari base URL is defined in two places, creating duplicate sources of truth between KENARI_BASE_URL in the fetcher module and the hardcoded baseURL in the Kenari provider constructor. Export and reuse the shared KENARI_BASE_URL constant from the fetcher module inside the Kenari provider so both paths always use the same endpoint, and keep the provider constructor aligned with that shared symbol.src/core/webview/webviewMessageHandler.ts (1)
1156-1172: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated "public provider candidate" wiring (same as opencode-go block above).
Lines 1156-1172 mirror lines 1139-1154 almost verbatim (comment text included). Consider extracting a small helper (e.g.
pushPublicProviderCandidate(candidates, key, resolvedKey, flushCondition)) since this pattern will likely repeat for future public dynamic providers.♻️ Sketch of a shared helper
+const pushPublicKeyedCandidate = async ( + candidates: { key: RouterName; options: GetModelsOptions }[], + routerKey: RouterName, + apiKey: string | undefined, + explicitKeyProvided: boolean, +) => { + if (explicitKeyProvided) { + await flushModels({ provider: routerKey, apiKey } as GetModelsOptions, true) + } + candidates.push({ key: routerKey, options: { provider: routerKey, apiKey } as GetModelsOptions }) +}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/core/webview/webviewMessageHandler.ts` around lines 1156 - 1172, The Kenari candidate setup is duplicating the same “public provider candidate” wiring already used in the opencode-go block, so extract the shared logic into a small helper (for example around the candidate push/flush flow in `webviewMessageHandler.ts`). Move the repeated key resolution, optional `flushModels` call, and `candidates.push` behavior into that helper, then reuse it for both the `kenari` and `opencode-go` paths so future public dynamic providers can follow the same pattern without copy-paste.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/api/providers/fetchers/kenari.ts`:
- Line 7: The Kenari base URL is defined in two places, creating duplicate
sources of truth between KENARI_BASE_URL in the fetcher module and the hardcoded
baseURL in the Kenari provider constructor. Export and reuse the shared
KENARI_BASE_URL constant from the fetcher module inside the Kenari provider so
both paths always use the same endpoint, and keep the provider constructor
aligned with that shared symbol.
In `@src/core/webview/webviewMessageHandler.ts`:
- Around line 1156-1172: The Kenari candidate setup is duplicating the same
“public provider candidate” wiring already used in the opencode-go block, so
extract the shared logic into a small helper (for example around the candidate
push/flush flow in `webviewMessageHandler.ts`). Move the repeated key
resolution, optional `flushModels` call, and `candidates.push` behavior into
that helper, then reuse it for both the `kenari` and `opencode-go` paths so
future public dynamic providers can follow the same pattern without copy-paste.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 3570c048-727a-4a48-a722-5c0b66d0506e
📒 Files selected for processing (43)
packages/types/src/global-settings.tspackages/types/src/provider-settings.tspackages/types/src/providers/index.tspackages/types/src/providers/kenari.tssrc/api/index.tssrc/api/providers/__tests__/kenari.spec.tssrc/api/providers/fetchers/__tests__/kenari.spec.tssrc/api/providers/fetchers/kenari.tssrc/api/providers/fetchers/modelCache.tssrc/api/providers/index.tssrc/api/providers/kenari.tssrc/core/webview/__tests__/ClineProvider.spec.tssrc/core/webview/__tests__/webviewMessageHandler.spec.tssrc/core/webview/webviewMessageHandler.tssrc/shared/api.tswebview-ui/src/components/settings/ApiOptions.tsxwebview-ui/src/components/settings/ModelPicker.tsxwebview-ui/src/components/settings/constants.tswebview-ui/src/components/settings/providers/Kenari.tsxwebview-ui/src/components/settings/providers/__tests__/Kenari.spec.tsxwebview-ui/src/components/settings/providers/index.tswebview-ui/src/components/settings/utils/providerModelConfig.tswebview-ui/src/components/ui/hooks/useSelectedModel.tswebview-ui/src/i18n/locales/ca/settings.jsonwebview-ui/src/i18n/locales/de/settings.jsonwebview-ui/src/i18n/locales/en/settings.jsonwebview-ui/src/i18n/locales/es/settings.jsonwebview-ui/src/i18n/locales/fr/settings.jsonwebview-ui/src/i18n/locales/hi/settings.jsonwebview-ui/src/i18n/locales/id/settings.jsonwebview-ui/src/i18n/locales/it/settings.jsonwebview-ui/src/i18n/locales/ja/settings.jsonwebview-ui/src/i18n/locales/ko/settings.jsonwebview-ui/src/i18n/locales/nl/settings.jsonwebview-ui/src/i18n/locales/pl/settings.jsonwebview-ui/src/i18n/locales/pt-BR/settings.jsonwebview-ui/src/i18n/locales/ru/settings.jsonwebview-ui/src/i18n/locales/tr/settings.jsonwebview-ui/src/i18n/locales/vi/settings.jsonwebview-ui/src/i18n/locales/zh-CN/settings.jsonwebview-ui/src/i18n/locales/zh-TW/settings.jsonwebview-ui/src/utils/__tests__/validate.spec.tswebview-ui/src/utils/validate.ts
eb9af0e to
90a25b9
Compare
|
The platform-unit-test failure was in SettingsView.change-detection.spec.tsx ("resets cached provider state when a new import timestamp arrives", expected baseten / got deepseek), a file this PR does not touch; it passes locally on this branch (4/4) and the same test failed once on main's own CI when it landed in #726. Pushed a same-content commit to retrigger CI since I lack rerun permissions. |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
edelauna
left a comment
There was a problem hiding this comment.
Looks good - main comment would be about re-using extract-reasoning logic so we're not duplicating logic.
Before adding this provider can you also create a correspond PR in https://github.com/Zoo-Code-Org/Zoo-Code-Docs for the provider slug documentation.
|
|
||
| const models = await getKenariModels("k") | ||
|
|
||
| expect(Object.keys(models)).toEqual(["valid-model"]) |
There was a problem hiding this comment.
This only checks that valid-model survives the fallback parse — should it also assert models["valid-model"].contextWindow (the fixture sets context_window: 50000 at line 83)? As written, a mutation that broke field-mapping in this fallback branch specifically wouldn't be caught here.
There was a problem hiding this comment.
Good catch. Added expect(models["valid-model"].contextWindow).toBe(50000) right after the keys check, so a field-mapping regression in that fallback branch is now caught. (77173c2)
| * @returns Normalised model metadata suitable for the model picker. | ||
| */ | ||
| export const parseKenariModel = (model: KenariModel): ModelInfo => ({ | ||
| maxTokens: model.max_output_tokens ?? model.max_tokens ?? kenariDefaultModelInfo.maxTokens, |
There was a problem hiding this comment.
Is the max_output_tokens ?? max_tokens (and context_window ?? context_length) precedence intentional? No test currently supplies both fields with different values, so this fallback order isn't actually verified anywhere.
There was a problem hiding this comment.
Intentional, and now verified. The precedence matches the house pattern in opencode-go.ts (identical 4-field optional schema) and litellm.ts (max_output_tokens || max_tokens): prefer the explicit context_window/max_output_tokens, fall back to the context_length/max_tokens aliases. Kenari currently returns only context_length and no max-token field, so the first-position fields are forward-compat, but I kept them for consistency and added a parseKenariModel test that supplies both with differing values to lock the order. (77173c2)
| if (delta && "reasoning_content" in delta && delta.reasoning_content) { | ||
| yield { type: "reasoning", text: delta.reasoning_content as string } |
There was a problem hiding this comment.
src/api/providers/utils/extract-reasoning.ts already has extractReasoningFromDelta(delta) for this — it also falls back to the OpenRouter-style reasoning field and uses a proper typeof === "string" check instead of this cast. Worth reusing here like opencode-go.ts does?
There was a problem hiding this comment.
Done, switched to extractReasoningFromDelta(delta) exactly like opencode-go.ts. This drops the cast for the proper typeof === "string" check and gains the OpenRouter-style reasoning fallback; added a handler test covering that fallback path. (77173c2)
- Reuse the shared extractReasoningFromDelta helper in the stream handler instead of a hand-rolled reasoning_content cast, matching opencode-go; this also adds the OpenRouter-style `reasoning` fallback. New handler test covers the fallback path. - Fetcher spec: assert the surviving entry's contextWindow in the skip-invalid test so a field-mapping regression in that branch is caught, and add a parseKenariModel test that locks the context_window/max_output_tokens precedence over their aliases. - Hoist KENARI_BASE_URL into @roo-code/types so the handler baseURL and the fetcher endpoint share one source of truth.
|
Thanks for the review @edelauna. Pushed 77173c2 addressing all three:
Gates green locally: Also opened the companion docs PR as requested: Zoo-Code-Org/Zoo-Code-Docs#7 (provider page + |
Kenari is an Indonesian OpenAI-compatible gateway billed in Rupiah; one kn- key reaches Claude, GPT, DeepSeek, GLM, Kimi and more. Adds the provider page and registers it in index.json so it appears in the sidebar and the provider comparison table. Companion to the extension provider PR (Zoo-Code-Org/Zoo-Code#793).
Closes #792
What
Adds Kenari as a first-class dynamic provider. Kenari is an Indonesian OpenAI-compatible AI gateway billed in Rupiah (IDR): one
kn-API key covers Claude, GPT, DeepSeek, GLM, Kimi and more. It already works through the generic OpenAI Compatible provider; this gives it a named entry with the live model list auto-populated.How
Mirrors the Opencode Go provider (#319) file-by-file, adapted to current main:
src/api/providers/kenari.ts: handler onRouterProvider(streaming text,reasoning_content, tool-call partials, usage with cache-read tokens).src/api/providers/fetchers/kenari.ts: dynamic model list from the publichttps://kenari.id/v1/models(no key needed; the key is forwarded when present). Mapscontext_lengthandmodalities.input(image support). Pricing is intentionally not mapped: Kenari returns IDR (micro_idr_per_1m_tokens), and the ModelInfo price fields are USD, so a converted or raw value would be wrong; cost stays undefined instead.packages/types:kenari.ts(defaultglm-5-2, 1,048,576 context fallback matching the live default model), settings schema, discriminated union,modelIdKeys,MODELS_BY_PROVIDER,SECRET_STATE_KEYS.buildApiHandler,modelCache,webviewMessageHandler(fetched unconditionally like the other public-endpoint routers),dynamicProviderExtras.Kenari.tsxsettings component (key field + get-key CTA + ModelPicker),ApiOptions,PROVIDERSconstant,providerModelConfig,useSelectedModel,validate.ts, and thekenariApiKey/getKenariApiKeystrings across all 18 locales.Deliberately skipped from #319: the Anthropic-wire split machinery. Kenari serves every model over OpenAI
/chat/completions, so the default "openai" protocol is correct with no extra code.Testing
packages/typessuite,srcshared+providers sweep (1603 passed / 1 skipped), webview settings+hooks+utils sweep (592 passed), kenari-focused suites 216/216.check-typesclean in all three packages; eslint--max-warnings=0and prettier clean on every touched file.https://kenari.id/v1/modelspublic (24 models withcontext_lengthand modalities); chat completions verified with a realkn-key againsthttps://kenari.id/v1. Docs: https://kenari.id/docs, OpenAPI: https://kenari.id/openapi.json.Disclosure: I run kenari.id. Happy to provide a test key for verification.
Summary by CodeRabbit
kenariApiKey).