Skip to content

Feat/zoo gateway#229

Open
JamesRobert20 wants to merge 29 commits into
mainfrom
feat/zoo-gateway
Open

Feat/zoo gateway#229
JamesRobert20 wants to merge 29 commits into
mainfrom
feat/zoo-gateway

Conversation

@JamesRobert20
Copy link
Copy Markdown
Contributor

@JamesRobert20 JamesRobert20 commented May 21, 2026

Pull Request: Zoo Gateway Provider Integration

Description

This PR introduces Zoo Gateway as a new provider option in Zoo Code, enabling users to access AI models through the Zoo Code platform's unified API gateway.

Key Implementation Details

1. New Provider Architecture

  • src/api/providers/zoo-gateway.ts — Full provider handler extending RouterProvider with OpenAI-compatible API, prompt caching support for Claude models, and enrichment headers for request tracking (X-Zoo-Task-ID, X-Zoo-Mode, X-Zoo-Extension-Version, X-Zoo-Editor)

  • src/api/providers/fetchers/zoo-gateway.ts — Model fetcher that retrieves available models from the Zoo Gateway /models endpoint, reusing Vercel AI Gateway's Zod schemas and parsing logic since API formats are identical

  • packages/types/src/providers/zoo-gateway.ts — Type definitions with claude-sonnet-4 as default model, shared prompt caching model list with Vercel AI Gateway

2. Authentication Flow

Authentication uses the existing Zoo Code OAuth flow (via /auth-callback URI handler):

  1. User clicks "Sign in to Zoo Code" button in settings
  2. Browser opens Zoo Code website authentication page
  3. After successful auth, callback URL returns token, name, email, and image
  4. handleUri.ts processes callback across all ClineProvider instances (not just visible ones) to ensure token persistence even when sidebar is hidden
  5. Token is stored as zooSessionToken in zoo-gateway provider profiles

3. Multi-Profile Token Synchronization

A significant design decision was made to handle profile management robustly:

  • ClineProvider.handleZooCodeCallback() scans all profiles with apiProvider === "zoo-gateway" and updates tokens in each (profile names are user-renameable)
  • ensureZooGatewayProfileSeeded() handles migration for existing users who authenticated before Zoo Gateway was added — creates a profile or updates stale tokens on webview init
  • On sign-out, webviewMessageHandler.ts clears zooSessionToken from all zoo-gateway profiles, not just the active one
  • Model list fetching looks up zoo-gateway profiles even when zoo-gateway isn't the active provider, so the model picker populates correctly

4. Base URL Environment Handling

The gateway base URL is derived dynamically from ZOO_CODE_BASE_URL environment variable:

  • Production: https://www.zoocode.dev/api/gateway/v1
  • Staging/dev environments route to their respective backends
  • This ensures auth server and gateway always match

5. Settings UI

webview-ui/src/components/settings/providers/ZooGateway.tsx:

  • Shows Zoo Code account status with user email when authenticated
  • "Sign in to Zoo Code" button when unauthenticated
  • ModelPicker with dynamic model list fetched from gateway
  • Validation requires sign-in before use (validation.zooGatewaySignIn)

6. Validation Changes

webview-ui/src/utils/validate.ts:

  • Added zoo-gateway validation that checks zooCodeIsAuthenticated state
  • Returns validation error if user tries to use zoo-gateway without signing in

Trade-offs & Design Decisions

  1. Token in all profiles vs. single profile: Chose to update all zoo-gateway profiles rather than just one canonical profile. This prevents stale tokens in renamed/duplicate profiles from causing auth failures when model fetcher does .find() and hits the wrong profile first.

  2. Fire-and-forget profile seeding: ensureZooGatewayProfileSeeded() runs on webview init without blocking. If it fails, users can still re-authenticate manually.

  3. No auto-switch on sign-in: When user signs in, we update zoo-gateway profiles but do NOT switch the active provider. Users must explicitly select Zoo Gateway to avoid disrupting mid-conversation flows.

Test Procedure

Unit Tests Updated:

Manual Testing Steps:

  1. New User Flow:

    • Open Zoo Code extension with no saved credentials
    • Go to Settings → Providers → select "Zoo Gateway"
    • Verify "Sign in to Zoo Code" button appears
    • Verify validation error appears if trying to proceed without signing in
    • Click "Sign in to Zoo Code" → authenticate in browser
    • Verify callback returns to VS Code and shows "Authenticated as [name]"
    • Verify model picker populates with available models
    • Select a model and start a conversation
    • Verify API requests include enrichment headers (check Network tab or server logs)
  2. Existing User Migration:

    • User who previously signed into Zoo Code (has cached token) but no zoo-gateway profile
    • Open settings → select Zoo Gateway
    • Verify profile is auto-created with existing token (check that authentication shows immediately without re-sign-in)
  3. Multi-Profile Scenarios:

    • Create multiple zoo-gateway profiles with different names
    • Sign in once
    • Verify all profiles show authenticated state
    • Sign out
    • Verify all profiles have token cleared
  4. Sign-Out Flow:

    • While using zoo-gateway provider, click sign out
    • Verify token is cleared from active profile
    • Verify validation error appears when trying to use zoo-gateway

Documentation Updates

  • Yes, documentation updates are required:
    • New provider documentation page for Zoo Gateway
    • Authentication flow explanation
    • Available models reference

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Zoo Gateway provider for accessing Claude models through Zoo Code
    • Improved authentication token handling to work reliably across all active profiles
  • Documentation

    • Updated privacy policy to clarify telemetry collection for authenticated users and retention periods (7 days for free, unlimited for Pro+)
    • Added Zoo Gateway UI support across all supported languages

Review Change Stack

James Mtendamema added 29 commits May 15, 2026 15:04
…users and adjust telemetry retention policies
@JamesRobert20 JamesRobert20 requested a review from taltas as a code owner May 21, 2026 05:47
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

📝 Walkthrough

Walkthrough

This PR introduces Zoo Gateway as a new LLM provider, including type system registration, model discovery and caching, API completion handling, Zoo Code authentication with profile management, settings UI integration, and comprehensive multi-language localization. It also broadens telemetry eligibility to all authenticated users (free and paid).

Changes

Zoo Gateway Provider Integration

Layer / File(s) Summary
Provider type system and registry
packages/types/src/provider-settings.ts, packages/types/src/providers/zoo-gateway.ts, packages/types/src/providers/index.ts
zoo-gateway is registered as a dynamic provider with its own validation schema (zooSessionToken, zooGatewayModelId, zooGatewayBaseUrl), model constants (Claude Sonnet 4 with prompt-cache and image support), and mapped to the anthropic protocol.
Model fetching and caching
src/api/providers/fetchers/zoo-gateway.ts, src/api/providers/fetchers/modelCache.ts, src/shared/api.ts
Zoo Gateway model discovery is implemented via Zod-validated /models API fetch, with caching deliberately bypassed for zoo-gateway and model info parsed in Vercel AI Gateway format.
API completion handler
src/api/providers/zoo-gateway.ts, src/api/index.ts, src/api/providers/index.ts
ZooGatewayHandler streams and completes requests, converts Anthropic messages to OpenAI format, applies prompt-cache breakpoints, enriches headers with Zoo-specific metadata, and extracts usage/cost including cache tokens.
Zoo Code authentication and token propagation
src/activate/handleUri.ts, src/core/webview/ClineProvider.ts, src/core/webview/webviewMessageHandler.ts
OAuth callback broadcasts tokens to all provider instances; zoo-gateway profiles are seeded on init and updated during callback; sign-out clears tokens across all zoo-gateway profiles.
Settings UI and model selection
webview-ui/src/components/settings/providers/ZooGateway.tsx, webview-ui/src/components/settings/ApiOptions.tsx, webview-ui/src/components/settings/ModelPicker.tsx, webview-ui/src/components/settings/constants.ts
New Zoo Gateway provider component with sign-in/auth UI; integrated into API Options provider list; model picker supports zooGatewayModelId selection.
Configuration validation with auth context
webview-ui/src/utils/validate.ts, webview-ui/src/components/settings/ApiOptions.tsx, webview-ui/src/components/welcome/WelcomeViewProvider.tsx
Validation functions now accept zooCodeIsAuthenticated flag and enforce sign-in requirement for zoo-gateway when token is missing; auth state flows through settings and welcome screens.
Test coverage and localization
src/activate/__tests__/handleUri.spec.ts, src/core/webview/__tests__/ClineProvider.spec.ts, src/core/webview/__tests__/webviewMessageHandler.spec.ts, webview-ui/src/utils/__tests__/validate.spec.ts, webview-ui/src/i18n/locales/*/settings.json, PRIVACY.md, README.md, locales/*/README.md
Test mocks updated for zoo-gateway model fetching and auth flows; zoo-gateway UI strings added across 17 languages; privacy policy now states telemetry applies to all authenticated users with retention tiered by plan (7 days free, unlimited pro+).

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant UI as Settings UI
  participant Auth as ZooCode OAuth
  participant Provider as ClineProvider
  participant API as ZooGatewayHandler
  participant Gateway as Zoo Gateway
  
  User->>UI: Select zoo-gateway provider
  User->>UI: Click "Sign in to Zoo Code"
  UI->>Auth: Open OAuth flow
  Auth->>User: OAuth consent screen
  User->>Auth: Approve
  Auth-->>Provider: Token received
  Provider->>Provider: Broadcast to all instances
  Provider->>Provider: Seed/update zoo-gateway profiles
  Provider-->>UI: Auth state updated
  UI->>UI: Show "Authenticated as {{name}}"
  
  User->>API: Request completion with model
  API->>Gateway: POST /chat/completions (auth header)
  Gateway-->>API: Stream response chunks
  API-->>UI: Display streamed text
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Zoo-Code-Org/Zoo-Code#32: Foundation PR for Zoo Code observability and auth infrastructure that this PR extends with Zoo Gateway provider integration, token/profile management, and validation auth-gating.

Suggested reviewers

  • hannesrudolph
  • edelauna
  • taltas
  • navedmerchant

Poem

🐰 A gateway to Zoo's grand design appears,
With tokens flowing and auth shining clear,
Through profiles and models, the signals align,
Now all authenticated hearts may dine! ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Feat/zoo gateway' uses a vague format ('Feat/...') that doesn't clearly convey the main change to someone scanning PR history. Use a clearer title format like 'Add Zoo Gateway provider integration' or 'Support Zoo Gateway as API provider' to improve clarity and searchability.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The PR description is comprehensive, covering key implementation details, authentication flow, design decisions, test procedures, and documentation updates required.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/zoo-gateway

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/api/providers/fetchers/modelCache.ts (1)

130-147: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Cache bypass is incomplete for zoo-gateway.

getModels skips cache, but refreshModels still persists models. That reintroduces stale cross-auth-context data for this provider.

Suggested fix
 export const refreshModels = async (options: GetModelsOptions): Promise<ModelRecord> => {
 	const { provider } = options
+	const shouldSkipCache = provider === "zoo-gateway"
@@
-			// Update memory cache first
-			memoryCache.set(provider, models)
-
-			// Atomically write to disk (safeWriteJson handles atomic writes)
-			await writeModels(provider, models).catch((err) =>
-				console.error(`[refreshModels] Error writing ${provider} models to disk:`, err),
-			)
+			if (!shouldSkipCache) {
+				// Update memory cache first
+				memoryCache.set(provider, models)
+
+				// Atomically write to disk (safeWriteJson handles atomic writes)
+				await writeModels(provider, models).catch((err) =>
+					console.error(`[refreshModels] Error writing ${provider} models to disk:`, err),
+				)
+			}
🤖 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/modelCache.ts` around lines 130 - 147, The
zoo-gateway provider still gets written into the shared cache by refreshModels;
update refreshModels so it never persists models for provider === "zoo-gateway"
(use the existing shouldSkipCache boolean) — i.e., before calling
memoryCache.set (or any cache write), check !shouldSkipCache and skip the write
for zoo-gateway; ensure any other code paths in refreshModels that call
memoryCache.set are similarly guarded.
🧹 Nitpick comments (2)
src/activate/__tests__/handleUri.spec.ts (1)

65-82: ⚡ Quick win

Add a true multi-instance regression test.

This only proves the zero-instance branch. A regression back to updating just the visible provider would still pass because nothing here asserts that every entry from getAllInstances() receives the callback token.

🧪 Suggested test shape
+		const hiddenProvider = {
+			handleZooCodeCallback: vi.fn(),
+		} as any
+		mockGetAllInstances.mockReturnValue([mockVisibleProvider, hiddenProvider])
+
+		await handleUri({
+			path: "/auth-callback",
+			query: "token=zoo_ext_test_token",
+		} as any)
+
+		expect(mockVisibleProvider.handleZooCodeCallback).toHaveBeenCalledWith("zoo_ext_test_token")
+		expect(hiddenProvider.handleZooCodeCallback).toHaveBeenCalledWith("zoo_ext_test_token")

As per coding guidelines, **/{__tests__,tests,test}/**/*.{test,spec}.{ts,tsx,js}: Use package-local unit tests for pure logic, parsing, state transitions, validation, serialization, request construction, retry decisions, and error handling.

🤖 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/activate/__tests__/handleUri.spec.ts` around lines 65 - 82, The current
test only covers the zero-instance path; add a multi-instance regression test
for handleUri that ensures every instance returned by getAllInstances receives
the callback token. Mock getVisibleInstance to return one visible provider and
mock getAllInstances to return an array of two or more provider instances (each
with a spy on handleZooCodeCallback), call handleUri with the same auth-callback
query, and assert mockHandleZooCodeAuthCallback was called with the token,
mockSetZooCodeUserInfo was called with parsed user info, and that each returned
instance.handleZooCodeCallback was called with "zoo_ext_test_token" (and
optionally that the visible provider was also called).
src/core/webview/__tests__/webviewMessageHandler.spec.ts (1)

324-379: ⚡ Quick win

Exercise the new separate-profile Zoo Gateway lookup.

This only verifies that zoo-gateway appears in the aggregated response. The new fallback path never runs here because mockClineProvider has no providerSettingsManager, and mockGetModels still succeeds with undefined credentials.

Add a case with a non-zoo-gateway active config plus mocked providerSettingsManager.listConfig/getProfile, then assert getModels receives the recovered apiKey and baseUrl. As per coding guidelines, **/{__tests__,tests,test}/**/*.{test,spec}.{ts,tsx,js}: Use package-local unit tests for pure logic, parsing, state transitions, validation, serialization, request construction, retry decisions, and error handling.

🤖 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/__tests__/webviewMessageHandler.spec.ts` around lines 324 -
379, The test doesn’t exercise the new Zoo Gateway separate-profile fallback
path; update the spec for webviewMessageHandler to add a case where
mockClineProvider has a providerSettingsManager with a non-"zoo-gateway" active
config and stub providerSettingsManager.listConfig and
providerSettingsManager.getProfile to return a config that maps to a zoo-gateway
profile (with apiKey and baseUrl), then invoke webviewMessageHandler and assert
mockGetModels was called with an object containing provider: "zoo-gateway" plus
the recovered apiKey and baseUrl (and still verify the aggregated routerModels
includes "zoo-gateway"); reference webviewMessageHandler, mockClineProvider,
mockGetModels, providerSettingsManager.listConfig and
providerSettingsManager.getProfile to locate and implement the new assertion.
🤖 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.

Inline comments:
In `@PRIVACY.md`:
- Around line 43-48: Update the "Zoo Code Observability (All Authenticated
Users)" section to remove the phrase "unlimited retention" for Pro and higher
users and replace it with a bounded retention policy or an explicit legal basis
and deletion workflow; specifically, change the account-linked telemetry
retention statement to either a fixed maximum retention period (e.g., X
days/months) or state the legal basis plus a documented deletion/archival SLA
and how users can request deletion, and ensure the text mentions the scope (task
ID, provider/model, token counts, estimated cost) and any plan-specific
differences (Free: 7 days; Pro+: specified retention or deletion workflow) so
the privacy claim is not indefinite.

In `@src/api/providers/fetchers/zoo-gateway.ts`:
- Around line 70-72: The axios.get call that discovers models (the
axios.get<ZooGatewayModelsResponse>(`${baseURL}/models`, { headers }) call)
needs an explicit timeout to avoid hanging; add a timeout option (e.g. timeout:
MODEL_DISCOVERY_TIMEOUT_MS) to the request options object, define
MODEL_DISCOVERY_TIMEOUT_MS as a configurable constant or env-backed value
(fallback to a sensible default like 5000 ms), and pass that into the axios.get
call so model discovery will fail fast on network stalls.
- Around line 93-95: The current console.error call serializes the entire error
(JSON.stringify(error, Object.getOwnPropertyNames(error))) which can leak
sensitive headers/bearer tokens; change the logging to avoid dumping the full
error object—log only safe fields such as error.message, error.name, and
error.stack (or error.code) and/or use a sanitizer that strips
request/config/headers before logging. Replace the JSON.stringify usage in the
console.error call (the line that logs "Error fetching Zoo Gateway models...")
with a call that builds a safeError summary (excluding any request, config,
headers, or auth fields) or simply log error.message and a truncated stack to
ensure no bearer tokens are emitted.

In `@src/core/webview/webviewMessageHandler.ts`:
- Around line 2477-2501: The sign-out loop in webviewMessageHandler.ts only
updates the in-memory active Zoo Gateway profile when the persisted profile
still has zooSessionToken, which misses cases where disk is already clean but
the in-memory currentSettings/token is stale; modify the loop so you always
construct a cleanedProfile (e.g., clone profile and delete zooSessionToken) and,
if isThisProfileActive (use isZooGatewayActive and currentApiConfigName ===
entry.name), always call provider.upsertProviderProfile(entry.name,
cleanedProfile, true) to clear the in-memory handler, while retaining the
existing behavior of calling
provider.providerSettingsManager.saveConfig(entry.name, cleanedProfile) when the
persisted profile originally contained a token (or simply always persist if you
prefer); reference symbols: entry, profile, cleanedProfile, isThisProfileActive,
isZooGatewayActive, currentApiConfigName, provider.upsertProviderProfile,
provider.providerSettingsManager.saveConfig.

In `@webview-ui/src/components/settings/ApiOptions.tsx`:
- Around line 709-718: The provider switch to "zoo-gateway" in ApiOptions.tsx
doesn't initialize zooGatewayModelId because "zoo-gateway" is missing from
PROVIDER_MODEL_CONFIG; add a PROVIDER_MODEL_CONFIG entry for "zoo-gateway" that
provides the model field/key used to initialize/set zooGatewayModelId (so
setApiConfigurationField or the same init logic used for other providers runs
when selectedProvider === "zoo-gateway"), update any references in ApiOptions
(and the ZooGateway wrapper) to use that config key, and add a local webview-ui
test covering provider-switch behavior to ensure the dynamic-provider model id
is set on switch.

In `@webview-ui/src/i18n/locales/tr/settings.json`:
- Around line 908-909: The two Turkish strings use informal second-person;
change them to formal phrasing to match existing validation messages: update
"qwenCodeOauthPath" from "Geçerli bir OAuth kimlik bilgileri yolu sağlamalısın"
to a formal form like "Geçerli bir OAuth kimlik bilgileri yolu sağlamalısınız"
and update "zooGatewaySignIn" from "Zoo Gateway'i kullanmak için Zoo Code'a
giriş yapmalısın. Kimlik doğrulamak için 'Giriş Yap'a tıkla." to a formal
equivalent such as "Zoo Gateway'i kullanmak için Zoo Code'a giriş yapmalısınız.
Kimlik doğrulamak için 'Giriş Yap' düğmesine tıklayın." Ensure both keys
("qwenCodeOauthPath" and "zooGatewaySignIn") are replaced accordingly to keep
tone consistent.

---

Outside diff comments:
In `@src/api/providers/fetchers/modelCache.ts`:
- Around line 130-147: The zoo-gateway provider still gets written into the
shared cache by refreshModels; update refreshModels so it never persists models
for provider === "zoo-gateway" (use the existing shouldSkipCache boolean) —
i.e., before calling memoryCache.set (or any cache write), check
!shouldSkipCache and skip the write for zoo-gateway; ensure any other code paths
in refreshModels that call memoryCache.set are similarly guarded.

---

Nitpick comments:
In `@src/activate/__tests__/handleUri.spec.ts`:
- Around line 65-82: The current test only covers the zero-instance path; add a
multi-instance regression test for handleUri that ensures every instance
returned by getAllInstances receives the callback token. Mock getVisibleInstance
to return one visible provider and mock getAllInstances to return an array of
two or more provider instances (each with a spy on handleZooCodeCallback), call
handleUri with the same auth-callback query, and assert
mockHandleZooCodeAuthCallback was called with the token, mockSetZooCodeUserInfo
was called with parsed user info, and that each returned
instance.handleZooCodeCallback was called with "zoo_ext_test_token" (and
optionally that the visible provider was also called).

In `@src/core/webview/__tests__/webviewMessageHandler.spec.ts`:
- Around line 324-379: The test doesn’t exercise the new Zoo Gateway
separate-profile fallback path; update the spec for webviewMessageHandler to add
a case where mockClineProvider has a providerSettingsManager with a
non-"zoo-gateway" active config and stub providerSettingsManager.listConfig and
providerSettingsManager.getProfile to return a config that maps to a zoo-gateway
profile (with apiKey and baseUrl), then invoke webviewMessageHandler and assert
mockGetModels was called with an object containing provider: "zoo-gateway" plus
the recovered apiKey and baseUrl (and still verify the aggregated routerModels
includes "zoo-gateway"); reference webviewMessageHandler, mockClineProvider,
mockGetModels, providerSettingsManager.listConfig and
providerSettingsManager.getProfile to locate and implement the new assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ed06aec1-7fb4-4521-8e6a-45343efe1826

📥 Commits

Reviewing files that changed from the base of the PR and between 3bd1a80 and e5ae0b6.

📒 Files selected for processing (62)
  • PRIVACY.md
  • README.md
  • locales/ca/README.md
  • locales/de/README.md
  • locales/es/README.md
  • locales/fr/README.md
  • locales/hi/README.md
  • locales/id/README.md
  • locales/it/README.md
  • locales/ja/README.md
  • locales/ko/README.md
  • locales/nl/README.md
  • locales/pl/README.md
  • locales/pt-BR/README.md
  • locales/ru/README.md
  • locales/tr/README.md
  • locales/vi/README.md
  • locales/zh-CN/README.md
  • locales/zh-TW/README.md
  • packages/types/src/provider-settings.ts
  • packages/types/src/providers/index.ts
  • packages/types/src/providers/zoo-gateway.ts
  • src/activate/__tests__/handleUri.spec.ts
  • src/activate/handleUri.ts
  • src/api/index.ts
  • src/api/providers/fetchers/modelCache.ts
  • src/api/providers/fetchers/zoo-gateway.ts
  • src/api/providers/index.ts
  • src/api/providers/zoo-gateway.ts
  • src/core/webview/ClineProvider.ts
  • src/core/webview/__tests__/ClineProvider.spec.ts
  • src/core/webview/__tests__/webviewMessageHandler.spec.ts
  • src/core/webview/webviewMessageHandler.ts
  • src/services/zoo-telemetry.ts
  • src/shared/api.ts
  • webview-ui/src/components/settings/ApiOptions.tsx
  • webview-ui/src/components/settings/ModelPicker.tsx
  • webview-ui/src/components/settings/constants.ts
  • webview-ui/src/components/settings/providers/ZooGateway.tsx
  • webview-ui/src/components/settings/providers/index.ts
  • webview-ui/src/components/ui/hooks/useSelectedModel.ts
  • webview-ui/src/components/welcome/WelcomeViewProvider.tsx
  • webview-ui/src/i18n/locales/ca/settings.json
  • webview-ui/src/i18n/locales/de/settings.json
  • webview-ui/src/i18n/locales/en/settings.json
  • webview-ui/src/i18n/locales/es/settings.json
  • webview-ui/src/i18n/locales/fr/settings.json
  • webview-ui/src/i18n/locales/hi/settings.json
  • webview-ui/src/i18n/locales/id/settings.json
  • webview-ui/src/i18n/locales/it/settings.json
  • webview-ui/src/i18n/locales/ja/settings.json
  • webview-ui/src/i18n/locales/ko/settings.json
  • webview-ui/src/i18n/locales/nl/settings.json
  • webview-ui/src/i18n/locales/pl/settings.json
  • webview-ui/src/i18n/locales/pt-BR/settings.json
  • webview-ui/src/i18n/locales/ru/settings.json
  • webview-ui/src/i18n/locales/tr/settings.json
  • webview-ui/src/i18n/locales/vi/settings.json
  • webview-ui/src/i18n/locales/zh-CN/settings.json
  • webview-ui/src/i18n/locales/zh-TW/settings.json
  • webview-ui/src/utils/__tests__/validate.spec.ts
  • webview-ui/src/utils/validate.ts

Comment thread PRIVACY.md
Comment on lines +43 to +48
- **Zoo Code Observability (All Authenticated Users):** If you sign in to
Zoo Code, Zoo Code will send LLM usage telemetry to the Zoo Code backend
(zoocode.dev). This includes task ID, AI provider name, model name, token
counts (input/output/cache), and estimated cost. This data is linked to your
authenticated Zoo Code account. Free plan users have their telemetry retained
for 7 days; Pro and higher plan users have unlimited retention. You can stop
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Revisit “unlimited retention” for account-linked telemetry.

Stating indefinite retention for authenticated Pro+ users is a compliance risk for data-minimization/retention-limitation requirements. Add a bounded retention policy (or explicit legal basis + deletion workflow/SLAs) in this section.

🤖 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 `@PRIVACY.md` around lines 43 - 48, Update the "Zoo Code Observability (All
Authenticated Users)" section to remove the phrase "unlimited retention" for Pro
and higher users and replace it with a bounded retention policy or an explicit
legal basis and deletion workflow; specifically, change the account-linked
telemetry retention statement to either a fixed maximum retention period (e.g.,
X days/months) or state the legal basis plus a documented deletion/archival SLA
and how users can request deletion, and ensure the text mentions the scope (task
ID, provider/model, token counts, estimated cost) and any plan-specific
differences (Free: 7 days; Pro+: specified retention or deletion workflow) so
the privacy claim is not indefinite.

Comment on lines +70 to +72
const response = await axios.get<ZooGatewayModelsResponse>(`${baseURL}/models`, {
headers,
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add an explicit timeout to model discovery requests.

Without a timeout, this call can hang and stall provider initialization paths.

Suggested fix
 		const response = await axios.get<ZooGatewayModelsResponse>(`${baseURL}/models`, {
 			headers,
+			timeout: 15_000,
 		})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const response = await axios.get<ZooGatewayModelsResponse>(`${baseURL}/models`, {
headers,
})
const response = await axios.get<ZooGatewayModelsResponse>(`${baseURL}/models`, {
headers,
timeout: 15_000,
})
🤖 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/zoo-gateway.ts` around lines 70 - 72, The
axios.get call that discovers models (the
axios.get<ZooGatewayModelsResponse>(`${baseURL}/models`, { headers }) call)
needs an explicit timeout to avoid hanging; add a timeout option (e.g. timeout:
MODEL_DISCOVERY_TIMEOUT_MS) to the request options object, define
MODEL_DISCOVERY_TIMEOUT_MS as a configurable constant or env-backed value
(fallback to a sensible default like 5000 ms), and pass that into the axios.get
call so model discovery will fail fast on network stalls.

Comment on lines +93 to +95
console.error(
`Error fetching Zoo Gateway models: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t serialize full request errors for authenticated calls.

This can leak the bearer token from request headers into logs.

Suggested fix
 	} catch (error) {
-		console.error(
-			`Error fetching Zoo Gateway models: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
-		)
+		const err = error as { message?: string; response?: { status?: number; statusText?: string } }
+		console.error(
+			`Error fetching Zoo Gateway models: status=${err.response?.status ?? "unknown"} ${err.response?.statusText ?? ""} message=${err.message ?? "unknown error"}`,
+		)
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.error(
`Error fetching Zoo Gateway models: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
)
const err = error as { message?: string; response?: { status?: number; statusText?: string } }
console.error(
`Error fetching Zoo Gateway models: status=${err.response?.status ?? "unknown"} ${err.response?.statusText ?? ""} message=${err.message ?? "unknown error"}`,
)
🤖 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/zoo-gateway.ts` around lines 93 - 95, The current
console.error call serializes the entire error (JSON.stringify(error,
Object.getOwnPropertyNames(error))) which can leak sensitive headers/bearer
tokens; change the logging to avoid dumping the full error object—log only safe
fields such as error.message, error.name, and error.stack (or error.code) and/or
use a sanitizer that strips request/config/headers before logging. Replace the
JSON.stringify usage in the console.error call (the line that logs "Error
fetching Zoo Gateway models...") with a call that builds a safeError summary
(excluding any request, config, headers, or auth fields) or simply log
error.message and a truncated stack to ensure no bearer tokens are emitted.

Comment on lines +2477 to +2501
for (const entry of allProfiles) {
if (entry.apiProvider === "zoo-gateway") {
const profile = await provider.providerSettingsManager.getProfile({ name: entry.name })
if (profile.zooSessionToken) {
// Clear the token from the profile
const { zooSessionToken: _removed, ...cleanedProfile } = profile

// If this is the currently active profile, push to in-memory handler
// so the current Task's API handler doesn't retain the stale token.
const isThisProfileActive = isZooGatewayActive && currentApiConfigName === entry.name

if (isThisProfileActive) {
// Push cleared profile to in-memory handler
await provider.upsertProviderProfile(entry.name, cleanedProfile, true)
provider.log(
`[zooCodeSignOut] Cleared zooSessionToken from "${entry.name}" profile and updated in-memory handler`,
)
} else {
// Just persist to disk; this profile is not currently active
await provider.providerSettingsManager.saveConfig(entry.name, cleanedProfile)
provider.log(
`[zooCodeSignOut] Cleared zooSessionToken from "${entry.name}" profile`,
)
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Always clear the active Zoo Gateway profile in memory.

The in-memory upsertProviderProfile(..., true) only runs when the persisted profile still has zooSessionToken. If disk state is already clean but currentSettings still carries the token, sign-out skips the in-memory update and the active handler keeps stale auth.

🧹 Proposed fix
 					for (const entry of allProfiles) {
 						if (entry.apiProvider === "zoo-gateway") {
 							const profile = await provider.providerSettingsManager.getProfile({ name: entry.name })
-							if (profile.zooSessionToken) {
-								// Clear the token from the profile
-								const { zooSessionToken: _removed, ...cleanedProfile } = profile
-
-								// If this is the currently active profile, push to in-memory handler
-								// so the current Task's API handler doesn't retain the stale token.
-								const isThisProfileActive = isZooGatewayActive && currentApiConfigName === entry.name
-
-								if (isThisProfileActive) {
-									// Push cleared profile to in-memory handler
-									await provider.upsertProviderProfile(entry.name, cleanedProfile, true)
-									provider.log(
-										`[zooCodeSignOut] Cleared zooSessionToken from "${entry.name}" profile and updated in-memory handler`,
-									)
-								} else {
-									// Just persist to disk; this profile is not currently active
-									await provider.providerSettingsManager.saveConfig(entry.name, cleanedProfile)
-									provider.log(
-										`[zooCodeSignOut] Cleared zooSessionToken from "${entry.name}" profile`,
-									)
-								}
+							const { zooSessionToken: _removed, ...cleanedProfile } = profile
+							const isThisProfileActive = isZooGatewayActive && currentApiConfigName === entry.name
+
+							if (profile.zooSessionToken || isThisProfileActive) {
+								if (isThisProfileActive) {
+									await provider.upsertProviderProfile(entry.name, cleanedProfile, true)
+									provider.log(
+										`[zooCodeSignOut] Cleared zooSessionToken from "${entry.name}" profile and updated in-memory handler`,
+									)
+								} else {
+									await provider.providerSettingsManager.saveConfig(entry.name, cleanedProfile)
+									provider.log(
+										`[zooCodeSignOut] Cleared zooSessionToken from "${entry.name}" profile`,
+									)
+								}
 							}
 						}
 					}
🤖 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 2477 - 2501, The
sign-out loop in webviewMessageHandler.ts only updates the in-memory active Zoo
Gateway profile when the persisted profile still has zooSessionToken, which
misses cases where disk is already clean but the in-memory currentSettings/token
is stale; modify the loop so you always construct a cleanedProfile (e.g., clone
profile and delete zooSessionToken) and, if isThisProfileActive (use
isZooGatewayActive and currentApiConfigName === entry.name), always call
provider.upsertProviderProfile(entry.name, cleanedProfile, true) to clear the
in-memory handler, while retaining the existing behavior of calling
provider.providerSettingsManager.saveConfig(entry.name, cleanedProfile) when the
persisted profile originally contained a token (or simply always persist if you
prefer); reference symbols: entry, profile, cleanedProfile, isThisProfileActive,
isZooGatewayActive, currentApiConfigName, provider.upsertProviderProfile,
provider.providerSettingsManager.saveConfig.

Comment on lines +709 to +718
{selectedProvider === "zoo-gateway" && (
<ZooGateway
apiConfiguration={apiConfiguration}
setApiConfigurationField={setApiConfigurationField}
routerModels={routerModels}
organizationAllowList={organizationAllowList}
modelValidationError={modelValidationError}
simplifySettings={fromWelcomeView}
/>
)}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add provider-switch model initialization for Zoo Gateway.

When apiProvider is switched to "zoo-gateway" (Line 709), zooGatewayModelId is never initialized because "zoo-gateway" is missing from PROVIDER_MODEL_CONFIG. This can leave config in an invalid state and block completion flows that require a dynamic-provider model id.

Proposed fix
@@
 	vercelAiGatewayDefaultModelId,
+	zooGatewayDefaultModelId,
 	minimaxDefaultModelId,
@@
 				"vercel-ai-gateway": { field: "vercelAiGatewayModelId", default: vercelAiGatewayDefaultModelId },
+				"zoo-gateway": { field: "zooGatewayModelId", default: zooGatewayDefaultModelId },
 				openai: { field: "openAiModelId" },
As per coding guidelines, `webview-ui/src/**/*.{ts,tsx}` should use local webview-ui coverage for UI behavior wiring; this provider wiring path should be covered.
🤖 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 `@webview-ui/src/components/settings/ApiOptions.tsx` around lines 709 - 718,
The provider switch to "zoo-gateway" in ApiOptions.tsx doesn't initialize
zooGatewayModelId because "zoo-gateway" is missing from PROVIDER_MODEL_CONFIG;
add a PROVIDER_MODEL_CONFIG entry for "zoo-gateway" that provides the model
field/key used to initialize/set zooGatewayModelId (so setApiConfigurationField
or the same init logic used for other providers runs when selectedProvider ===
"zoo-gateway"), update any references in ApiOptions (and the ZooGateway wrapper)
to use that config key, and add a local webview-ui test covering provider-switch
behavior to ensure the dynamic-provider model id is set on switch.

Comment on lines +908 to +909
"qwenCodeOauthPath": "Geçerli bir OAuth kimlik bilgileri yolu sağlamalısın",
"zooGatewaySignIn": "Zoo Gateway'i kullanmak için Zoo Code'a giriş yapmalısın. Kimlik doğrulamak için 'Giriş Yap'a tıkla."
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use consistent formal tone in Turkish validation copy.

Line 908 and Line 909 switch to informal second-person wording, while nearby validation messages use formal phrasing. Please align these two strings to keep UX tone consistent.

✍️ Suggested wording
-		"qwenCodeOauthPath": "Geçerli bir OAuth kimlik bilgileri yolu sağlamalısın",
-		"zooGatewaySignIn": "Zoo Gateway'i kullanmak için Zoo Code'a giriş yapmalısın. Kimlik doğrulamak için 'Giriş Yap'a tıkla."
+		"qwenCodeOauthPath": "Geçerli bir OAuth kimlik bilgileri yolu sağlamalısınız",
+		"zooGatewaySignIn": "Zoo Gateway'i kullanmak için Zoo Code'a giriş yapmalısınız. Kimlik doğrulamak için 'Giriş Yap'a tıklayın."
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"qwenCodeOauthPath": "Geçerli bir OAuth kimlik bilgileri yolu sağlamalısın",
"zooGatewaySignIn": "Zoo Gateway'i kullanmak için Zoo Code'a giriş yapmalısın. Kimlik doğrulamak için 'Giriş Yap'a tıkla."
"qwenCodeOauthPath": "Geçerli bir OAuth kimlik bilgileri yolu sağlamalısınız",
"zooGatewaySignIn": "Zoo Gateway'i kullanmak için Zoo Code'a giriş yapmalısınız. Kimlik doğrulamak için 'Giriş Yap'a tıklayın."
🤖 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 `@webview-ui/src/i18n/locales/tr/settings.json` around lines 908 - 909, The two
Turkish strings use informal second-person; change them to formal phrasing to
match existing validation messages: update "qwenCodeOauthPath" from "Geçerli bir
OAuth kimlik bilgileri yolu sağlamalısın" to a formal form like "Geçerli bir
OAuth kimlik bilgileri yolu sağlamalısınız" and update "zooGatewaySignIn" from
"Zoo Gateway'i kullanmak için Zoo Code'a giriş yapmalısın. Kimlik doğrulamak
için 'Giriş Yap'a tıkla." to a formal equivalent such as "Zoo Gateway'i
kullanmak için Zoo Code'a giriş yapmalısınız. Kimlik doğrulamak için 'Giriş Yap'
düğmesine tıklayın." Ensure both keys ("qwenCodeOauthPath" and
"zooGatewaySignIn") are replaced accordingly to keep tone consistent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant