Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4f77882
first pass
May 15, 2026
698ac9d
refactor
May 15, 2026
8dec8a9
fix missing items
May 15, 2026
835c0b8
misc fix
May 15, 2026
f59f98f
prevent auto provider switch
May 15, 2026
7a032a5
fix
May 16, 2026
0222517
using correct url
May 16, 2026
62e662f
ensure authentication before Zoo Gateway usage
May 16, 2026
5dd8c89
refactor: improve Zoo Gateway profile handling during session token u…
May 16, 2026
af52468
refactor: enhance zooSessionToken clearing for all profiles using zoo…
May 16, 2026
bb35707
refactor: update profile name handling for Zoo Gateway based on activ…
May 16, 2026
348fb02
refactor: add Zoo Gateway sign-in validation and update localization …
May 16, 2026
8a96a15
refactor: update Zoo Code observability to include all authenticated …
May 19, 2026
3b6d780
refactor: update Zoo Gateway base URL handling to use getZooCodeBaseUrl
May 19, 2026
71e0fbc
refactor: update handleZooCodeCallback to persist zooSessionToken acr…
May 19, 2026
4c14d26
refactor: add profile seeding for zoo-gateway users with cached auth …
May 19, 2026
9d3966a
refactor: enhance mock setup in handleUri tests to include getAllInst…
May 19, 2026
14f4771
refactor: enhance zoo-gateway token retrieval to support non-active p…
May 19, 2026
a0e95cc
refactor: enhance ensureZooGatewayProfileSeeded to handle profiles wi…
May 19, 2026
17f6467
refactor: enhance ensureZooGatewayProfileSeeded to validate current t…
May 19, 2026
8d86f55
refactor: update zoo-gateway profiles to synchronize tokens across al…
May 20, 2026
24e0f75
refactor: add zoo-gateway to mock models in ClineProvider and webview…
May 20, 2026
c37a35f
Merge remote-tracking branch 'origin/main' into feat/zoo-gateway
May 20, 2026
cd30577
refactor: enhance zoo-gateway profile token validation and seeding logic
May 20, 2026
e1ee061
refactor: simplify ZooGateway component by removing unused input hand…
May 20, 2026
fe2e2ef
Merge remote-tracking branch 'origin/main' into feat/zoo-gateway
May 20, 2026
48521e6
Merge remote-tracking branch 'origin/main' into feat/zoo-gateway
May 21, 2026
8f03729
fix auth in settings for zoo gateway
May 21, 2026
e5ae0b6
fix: prevent caching of models for zoo-gateway and update service URL…
May 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions PRIVACY.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,12 @@ go—and, importantly, where they don't.
We retain telemetry only as long as needed for product analytics and debugging.
Telemetry does **not** collect your code or AI prompts, and you can opt out at
any time through the settings.
- **Zoo Code Observability (Authenticated Subscribers Only):** If you sign in to
Zoo Code and have an active subscription, 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. You can stop
- **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
Comment on lines +43 to +48
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.

this collection at any time by signing out via the Zoo Code badge in the chat
area.
- **Marketplace Requests**: When you browse or search the Marketplace for Model
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ pre-release builds published automatically on every merge to `main`.
- [简体中文](locales/zh-CN/README.md)
- [繁體中文](locales/zh-TW/README.md)
- ...
</details>
</details>

---

Expand Down
2 changes: 1 addition & 1 deletion locales/ca/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/de/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/es/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/fr/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/hi/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/id/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/it/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/ja/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/ko/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/nl/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/pl/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/pt-BR/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/ru/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/tr/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/vi/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/zh-CN/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/zh-TW/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 14 additions & 2 deletions packages/types/src/provider-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const DEFAULT_CONSECUTIVE_MISTAKE_LIMIT = 3
export const dynamicProviders = [
"openrouter",
"vercel-ai-gateway",
"zoo-gateway",
"litellm",
"requesty",
"roo",
Expand Down Expand Up @@ -405,6 +406,12 @@ const vercelAiGatewaySchema = baseProviderSettingsSchema.extend({
vercelAiGatewayModelId: z.string().optional(),
})

const zooGatewaySchema = baseProviderSettingsSchema.extend({
zooSessionToken: z.string().optional(),
zooGatewayModelId: z.string().optional(),
zooGatewayBaseUrl: z.string().optional(),
})

const basetenSchema = apiModelIdProviderModelSchema.extend({
basetenApiKey: z.string().optional(),
})
Expand Down Expand Up @@ -444,6 +451,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv
qwenCodeSchema.merge(z.object({ apiProvider: z.literal("qwen-code") })),
rooSchema.merge(z.object({ apiProvider: z.literal("roo") })),
vercelAiGatewaySchema.merge(z.object({ apiProvider: z.literal("vercel-ai-gateway") })),
zooGatewaySchema.merge(z.object({ apiProvider: z.literal("zoo-gateway") })),
defaultSchema,
])

Expand Down Expand Up @@ -479,6 +487,7 @@ export const providerSettingsSchema = z.object({
...qwenCodeSchema.shape,
...rooSchema.shape,
...vercelAiGatewaySchema.shape,
...zooGatewaySchema.shape,
...codebaseIndexProviderSchema.shape,
})

Expand Down Expand Up @@ -509,6 +518,7 @@ export const modelIdKeys = [
"unboundModelId",
"litellmModelId",
"vercelAiGatewayModelId",
"zooGatewayModelId",
] as const satisfies readonly (keyof ProviderSettings)[]

export type ModelIdKey = (typeof modelIdKeys)[number]
Expand Down Expand Up @@ -555,6 +565,7 @@ export const modelIdKeysByProvider: Record<TypicalProvider, ModelIdKey> = {
fireworks: "apiModelId",
roo: "apiModelId",
"vercel-ai-gateway": "vercelAiGatewayModelId",
"zoo-gateway": "zooGatewayModelId",
}

/**
Expand All @@ -573,10 +584,10 @@ export const getApiProtocol = (provider: ProviderName | undefined, modelId?: str
return "anthropic"
}

// Vercel AI Gateway uses anthropic protocol for anthropic models.
// Vercel AI Gateway, Zoo Gateway, and Roo use anthropic protocol for anthropic models.
if (
provider &&
["vercel-ai-gateway", "roo"].includes(provider) &&
["vercel-ai-gateway", "zoo-gateway", "roo"].includes(provider) &&
modelId &&
modelId.toLowerCase().startsWith("anthropic/")
) {
Expand Down Expand Up @@ -677,6 +688,7 @@ export const MODELS_BY_PROVIDER: Record<
requesty: { id: "requesty", label: "Requesty", models: [] },
unbound: { id: "unbound", label: "Unbound", models: [] },
"vercel-ai-gateway": { id: "vercel-ai-gateway", label: "Vercel AI Gateway", models: [] },
"zoo-gateway": { id: "zoo-gateway", label: "Zoo Gateway", models: [] },

// Local providers; models discovered from localhost endpoints.
lmstudio: { id: "lmstudio", label: "LM Studio", models: [] },
Expand Down
4 changes: 4 additions & 0 deletions packages/types/src/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export * from "./vercel-ai-gateway.js"
export * from "./zai.js"
export * from "./minimax.js"
export * from "./mimo.js"
export * from "./zoo-gateway.js"

import { anthropicDefaultModelId } from "./anthropic.js"
import { basetenDefaultModelId } from "./baseten.js"
Expand All @@ -51,6 +52,7 @@ import { vercelAiGatewayDefaultModelId } from "./vercel-ai-gateway.js"
import { internationalZAiDefaultModelId, mainlandZAiDefaultModelId } from "./zai.js"
import { minimaxDefaultModelId } from "./minimax.js"
import { mimoDefaultModelId } from "./mimo.js"
import { zooGatewayDefaultModelId } from "./zoo-gateway.js"

// Import the ProviderName type from provider-settings to avoid duplication
import type { ProviderName } from "../provider-settings.js"
Expand Down Expand Up @@ -119,6 +121,8 @@ export function getProviderDefaultModelId(
return unboundDefaultModelId
case "vercel-ai-gateway":
return vercelAiGatewayDefaultModelId
case "zoo-gateway":
return zooGatewayDefaultModelId
case "anthropic":
case "gemini-cli":
case "fake-ai":
Expand Down
24 changes: 24 additions & 0 deletions packages/types/src/providers/zoo-gateway.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { ModelInfo } from "../model.js"

// Zoo Gateway uses the same model ID format as Vercel AI Gateway (provider/model-name)
export const zooGatewayDefaultModelId = "anthropic/claude-sonnet-4"

// Zoo Gateway serves the same models as Vercel AI Gateway, so prompt caching support is identical
// We reuse VERCEL_AI_GATEWAY_PROMPT_CACHING_MODELS from vercel-ai-gateway.ts
// Instead of duplicating, we just export a reference to indicate they're the same
export { VERCEL_AI_GATEWAY_PROMPT_CACHING_MODELS as ZOO_GATEWAY_PROMPT_CACHING_MODELS } from "./vercel-ai-gateway.js"

export const zooGatewayDefaultModelInfo: ModelInfo = {
maxTokens: 64000,
contextWindow: 200000,
supportsImages: true,
supportsPromptCache: true,
inputPrice: 3,
outputPrice: 15,
cacheWritesPrice: 3.75,
cacheReadsPrice: 0.3,
description:
"Claude Sonnet 4 significantly improves on Sonnet 3.7's industry-leading capabilities, excelling in coding with a state-of-the-art 72.7% on SWE-bench. The model balances performance and efficiency for internal and external use cases, with enhanced steerability for greater control over implementations.",
}

export const ZOO_GATEWAY_DEFAULT_TEMPERATURE = 0.7
42 changes: 26 additions & 16 deletions src/activate/__tests__/handleUri.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,32 @@ vi.mock("vscode", () => ({

import * as vscode from "vscode"

const { mockGetVisibleInstance, mockHandleZooCodeAuthCallback, mockSetZooCodeUserInfo, mockVisibleProvider } =
vi.hoisted(() => {
const mockVisibleProvider = {
handleOpenRouterCallback: vi.fn(),
handleRequestyCallback: vi.fn(),
handleZooCodeCallback: vi.fn(),
} as any

return {
mockGetVisibleInstance: vi.fn(() => mockVisibleProvider),
mockHandleZooCodeAuthCallback: vi.fn(),
mockSetZooCodeUserInfo: vi.fn(),
mockVisibleProvider,
}
})
const {
mockGetVisibleInstance,
mockGetAllInstances,
mockHandleZooCodeAuthCallback,
mockSetZooCodeUserInfo,
mockVisibleProvider,
} = vi.hoisted(() => {
const mockVisibleProvider = {
handleOpenRouterCallback: vi.fn(),
handleRequestyCallback: vi.fn(),
handleZooCodeCallback: vi.fn(),
} as any

return {
mockGetVisibleInstance: vi.fn(() => mockVisibleProvider),
mockGetAllInstances: vi.fn(() => [mockVisibleProvider]),
mockHandleZooCodeAuthCallback: vi.fn(),
mockSetZooCodeUserInfo: vi.fn(),
mockVisibleProvider,
}
})

vi.mock("../../core/webview/ClineProvider", () => ({
ClineProvider: {
getVisibleInstance: mockGetVisibleInstance,
getAllInstances: mockGetAllInstances,
},
}))

Expand All @@ -39,6 +46,7 @@ describe("handleUri", () => {
beforeEach(() => {
vi.clearAllMocks()
mockGetVisibleInstance.mockReturnValue(mockVisibleProvider)
mockGetAllInstances.mockReturnValue([mockVisibleProvider])
})

it("ignores legacy cloud auth callback", async () => {
Expand All @@ -54,8 +62,9 @@ describe("handleUri", () => {
)
})

it("stores callback user info even when no webview is visible", async () => {
it("stores callback user info even when no provider instances exist", async () => {
mockGetVisibleInstance.mockReturnValue(null)
mockGetAllInstances.mockReturnValue([])
mockHandleZooCodeAuthCallback.mockResolvedValue(true)

await handleUri({
Expand All @@ -69,6 +78,7 @@ describe("handleUri", () => {
email: "jane@example.com",
image: "https://example.com/avatar.png",
})
// No provider instances exist, so handleZooCodeCallback should not be called
expect(mockVisibleProvider.handleZooCodeCallback).not.toHaveBeenCalled()
})

Expand Down
10 changes: 6 additions & 4 deletions src/activate/handleUri.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,12 @@ export const handleUri = async (uri: vscode.Uri) => {
email,
image,
})
// Refresh webview state if a panel is currently open
if (visibleProvider) {
await visibleProvider.handleZooCodeCallback(token)
}
// Write the token to all active provider instances regardless of visibility.
// The profile settings write (handleZooCodeCallback) must run on any active
// instance — not just the visible one — so the zoo-gateway zooSessionToken
// is persisted even when the sidebar/panel is hidden at callback time.
const allInstances = ClineProvider.getAllInstances()
await Promise.all(allInstances.map((instance) => instance.handleZooCodeCallback(token)))
}
}
break
Expand Down
Loading
Loading