Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 0 additions & 7 deletions PRIVACY.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,6 @@ 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
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
Configuration Profiles (MCPs) or Custom Modes, Zoo Code makes a secure API
call to Zoo Code's backend servers to retrieve listing information. These
Expand Down
21 changes: 0 additions & 21 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3013,27 +3013,6 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
cacheReadTokens: tokens.cacheRead,
cost: tokens.total ?? costResult.totalCost,
})

// Zoo Code observability telemetry
import("../../services/zoo-telemetry")
.then(async ({ sendLlmTelemetry }) => {
const mode = await this.getTaskMode().catch(() => "unknown")
return sendLlmTelemetry({
taskId: this.taskId,
provider: this.apiConfiguration?.apiProvider ?? "unknown",
model: this.apiConfiguration
? (getModelId(this.apiConfiguration) ?? "unknown")
: "unknown",
mode,
inputTokens: costResult.totalInputTokens,
outputTokens: costResult.totalOutputTokens,
cacheReadTokens: tokens.cacheRead ?? 0,
cacheWriteTokens: tokens.cacheWrite ?? 0,
totalCost: tokens.total ?? costResult.totalCost,
status,
}).catch(() => {})
})
.catch(() => {})
}
}

Expand Down
147 changes: 9 additions & 138 deletions src/services/__tests__/zoo-code-auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
import * as vscode from "vscode"

import {
checkSubscriptionStatus,
clearZooCodeToken,
clearZooCodeUserInfo,
disconnectZooCode,
getCachedSubscriptionStatus,
getCachedZooCodeToken,
getCachedZooCodeUserInfo,
getZooCodeBaseUrl,
Expand Down Expand Up @@ -68,98 +66,6 @@ describe("zoo-code-auth", () => {
vi.restoreAllMocks()
})

describe("getCachedSubscriptionStatus", () => {
it("returns unknown initially", () => {
expect(getCachedSubscriptionStatus()).toBe("unknown")
})
})

describe("checkSubscriptionStatus", () => {
it("returns inactive when no token is present", async () => {
await initZooCodeAuth(mockContext)

const status = await checkSubscriptionStatus()

expect(status).toBe("inactive")
expect(mockFetch).not.toHaveBeenCalled()
})

it("returns active when the API reports an active subscriber", async () => {
await initZooCodeAuth(mockContext)
await setZooCodeToken("zoo_ext_test_token")

mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({ isSubscriber: true, planId: "pro", status: "active" }),
})

const status = await checkSubscriptionStatus()

expect(status).toBe("active")
expect(mockFetch).toHaveBeenCalledWith(
expect.stringContaining("/api/subscription/status"),
expect.objectContaining({
headers: { Authorization: "Bearer zoo_ext_test_token" },
}),
)
})

it("returns inactive when the API reports a free user", async () => {
await initZooCodeAuth(mockContext)
await setZooCodeToken("zoo_ext_test_token")

mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({ isSubscriber: false, planId: "free", status: "active" }),
})

await expect(checkSubscriptionStatus()).resolves.toBe("inactive")
})

it("returns unknown when the API request fails", async () => {
await initZooCodeAuth(mockContext)
await setZooCodeToken("zoo_ext_test_token")

mockFetch.mockResolvedValueOnce({
ok: false,
status: 500,
statusText: "Internal Server Error",
})

await expect(checkSubscriptionStatus()).resolves.toBe("unknown")
})

it("returns unknown when the API throws", async () => {
await initZooCodeAuth(mockContext)
await setZooCodeToken("zoo_ext_test_token")
mockFetch.mockRejectedValueOnce(new Error("Network error"))

await expect(checkSubscriptionStatus()).resolves.toBe("unknown")
})

it("reuses the cached status when it was checked recently", async () => {
await initZooCodeAuth(mockContext)
await setZooCodeToken("zoo_ext_test_token")

mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({ isSubscriber: true, planId: "pro", status: "active" }),
})

expect(await checkSubscriptionStatus()).toBe("active")
expect(await checkSubscriptionStatus()).toBe("active")
expect(mockFetch).toHaveBeenCalledTimes(1)
})

it("handles AbortSignal timeouts", async () => {
await initZooCodeAuth(mockContext)
await setZooCodeToken("zoo_ext_test_token")
mockFetch.mockRejectedValueOnce(new DOMException("Aborted", "AbortError"))

await expect(checkSubscriptionStatus()).resolves.toBe("unknown")
})
})

describe("getCachedZooCodeToken", () => {
it("returns an empty string when no token is set", async () => {
await clearZooCodeToken()
Expand All @@ -169,15 +75,10 @@ describe("zoo-code-auth", () => {

it("preloads the cached token during initialization", async () => {
await mockSecrets.store("zoo-code-session-token", "zoo_ext_cached_token")
mockFetch
.mockResolvedValueOnce({
ok: true,
json: async () => ({ valid: true }),
})
.mockResolvedValueOnce({
ok: true,
json: async () => ({ isSubscriber: true }),
})
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({ valid: true }),
})

await initZooCodeAuth(mockContext)
await Promise.resolve()
Expand Down Expand Up @@ -237,10 +138,8 @@ describe("zoo-code-auth", () => {

await initZooCodeAuth(mockContext)

// Token and user info should be kept; subscription status should be unknown
expect(getCachedZooCodeToken()).toBe("zoo_ext_valid_token")
expect(getCachedZooCodeUserInfo().name).toBe("Jane Doe")
expect(getCachedSubscriptionStatus()).toBe("unknown")
})

it("preserves token and user info when verify returns 5xx (transient backend error)", async () => {
Expand All @@ -257,39 +156,16 @@ describe("zoo-code-auth", () => {

expect(getCachedZooCodeToken()).toBe("zoo_ext_valid_token")
expect(getCachedZooCodeUserInfo().name).toBe("Jane Doe")
expect(getCachedSubscriptionStatus()).toBe("unknown")
})
})

describe("setZooCodeToken", () => {
it("resets the cached subscription status when the token changes", async () => {
await initZooCodeAuth(mockContext)
await setZooCodeToken("zoo_ext_token1")
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({ isSubscriber: true, planId: "pro", status: "active" }),
})
await checkSubscriptionStatus()

await setZooCodeToken("zoo_ext_token2")

expect(getCachedSubscriptionStatus()).toBe("unknown")
})
})

describe("clearZooCodeToken", () => {
it("resets the cached subscription status when the token is cleared", async () => {
it("clears the cached token", async () => {
await initZooCodeAuth(mockContext)
await setZooCodeToken("zoo_ext_test_token")
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({ isSubscriber: true, planId: "pro", status: "active" }),
})
await checkSubscriptionStatus()

await clearZooCodeToken()

expect(getCachedSubscriptionStatus()).toBe("unknown")
expect(getCachedZooCodeToken()).toBe("")
})
})
Expand Down Expand Up @@ -337,15 +213,10 @@ describe("zoo-code-auth", () => {

it("persists a token only after backend verification succeeds", async () => {
await initZooCodeAuth(mockContext)
mockFetch
.mockResolvedValueOnce({
ok: true,
json: async () => ({ valid: true }),
})
.mockResolvedValueOnce({
ok: true,
json: async () => ({ isSubscriber: true }),
})
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({ valid: true }),
})

const success = await handleAuthCallback("zoo_ext_real_token")

Expand Down
132 changes: 0 additions & 132 deletions src/services/__tests__/zoo-telemetry.test.ts

This file was deleted.

Loading
Loading