Skip to content

Commit 23859cc

Browse files
James Mtendamemacursoragent
andcommitted
fix(zoo-gateway): address PR review feedback on auth, seeding, and errors
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 004b3fd commit 23859cc

7 files changed

Lines changed: 82 additions & 20 deletions

File tree

src/activate/__tests__/handleUri.spec.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,4 +177,24 @@ describe("handleUri", () => {
177177

178178
expect(order).toEqual(["a:start", "a:end", "b:start", "b:end"])
179179
})
180+
181+
it("continues fan-out when one instance fails to persist the callback token", async () => {
182+
mockHandleZooCodeAuthCallback.mockResolvedValue(true)
183+
184+
const failingProvider = {
185+
handleZooCodeCallback: vi.fn(async () => {
186+
throw new Error("profile store unavailable")
187+
}),
188+
} as any
189+
const healthyProvider = { handleZooCodeCallback: vi.fn() } as any
190+
mockGetAllInstances.mockReturnValue([failingProvider, healthyProvider])
191+
192+
await handleUri({
193+
path: "/auth-callback",
194+
query: "token=zoo_ext_test_token",
195+
} as any)
196+
197+
expect(failingProvider.handleZooCodeCallback).toHaveBeenCalledWith("zoo_ext_test_token")
198+
expect(healthyProvider.handleZooCodeCallback).toHaveBeenCalledWith("zoo_ext_test_token")
199+
})
180200
})

src/activate/handleUri.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,14 @@ export const handleUri = async (uri: vscode.Uri) => {
6363
// is cheap (at most a handful of instances) and avoids the race.
6464
const allInstances = ClineProvider.getAllInstances()
6565
for (const instance of allInstances) {
66-
await instance.handleZooCodeCallback(token)
66+
try {
67+
await instance.handleZooCodeCallback(token)
68+
} catch (error) {
69+
console.error(
70+
"Failed to persist Zoo Gateway token for a provider instance:",
71+
error instanceof Error ? error.message : error,
72+
)
73+
}
6774
}
6875
}
6976
}

src/api/providers/__tests__/zoo-gateway.spec.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,11 @@ vitest.mock("../fetchers/modelCache", () => ({
5656
getModelsFromCache: vitest.fn().mockReturnValue(undefined),
5757
}))
5858

59+
const mockGetCachedZooCodeToken = vitest.hoisted(() => vitest.fn<() => string | undefined>(() => undefined))
60+
5961
vitest.mock("../../../services/zoo-code-auth", () => ({
6062
getZooCodeBaseUrl: vitest.fn(() => "https://www.zoocode.dev"),
61-
getCachedZooCodeToken: vitest.fn(() => undefined),
63+
getCachedZooCodeToken: mockGetCachedZooCodeToken,
6264
clearZooCodeToken: vitest.fn(async () => undefined),
6365
}))
6466

@@ -91,6 +93,7 @@ describe("ZooGatewayHandler", () => {
9193

9294
beforeEach(() => {
9395
vitest.clearAllMocks()
96+
mockGetCachedZooCodeToken.mockReturnValue(undefined)
9497
mockCreate.mockClear()
9598
showErrorMessage.mockReset()
9699
showErrorMessage.mockResolvedValue(undefined)
@@ -128,6 +131,21 @@ describe("ZooGatewayHandler", () => {
128131
)
129132
})
130133

134+
it("prefers the secret-storage cache over a persisted profile token", () => {
135+
mockGetCachedZooCodeToken.mockReturnValue("zoo_ext_cached_token")
136+
137+
new ZooGatewayHandler({
138+
zooSessionToken: "zoo_ext_stale_profile_token",
139+
zooGatewayModelId: mockOptions.zooGatewayModelId,
140+
})
141+
142+
expect(OpenAI).toHaveBeenCalledWith(
143+
expect.objectContaining({
144+
apiKey: "zoo_ext_cached_token",
145+
}),
146+
)
147+
})
148+
131149
it("initializes OpenAI with Zoo enrichment headers and session token", () => {
132150
const handler = new ZooGatewayHandler({
133151
...mockOptions,

src/api/providers/zoo-gateway.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,12 @@ export class ZooGatewayHandler extends RouterProvider implements SingleCompletio
217217
}
218218
}
219219
} catch (error) {
220-
void surfaceGatewayApiError(error)
220+
void surfaceGatewayApiError(error).catch((surfaceError) => {
221+
console.error(
222+
"Failed to surface Zoo Gateway error:",
223+
surfaceError instanceof Error ? surfaceError.message : surfaceError,
224+
)
225+
})
221226
throw error
222227
}
223228
}
@@ -243,7 +248,12 @@ export class ZooGatewayHandler extends RouterProvider implements SingleCompletio
243248
const response = await this.client.chat.completions.create(requestOptions)
244249
return response.choices[0]?.message.content || ""
245250
} catch (error) {
246-
void surfaceGatewayApiError(error)
251+
void surfaceGatewayApiError(error).catch((surfaceError) => {
252+
console.error(
253+
"Failed to surface Zoo Gateway error:",
254+
surfaceError instanceof Error ? surfaceError.message : surfaceError,
255+
)
256+
})
247257
if (error instanceof Error) {
248258
throw new Error(`Zoo Gateway completion error: ${error.message}`)
249259
}

src/core/webview/ClineProvider.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -871,9 +871,10 @@ export class ClineProvider
871871
* Called once per webview init; handleZooCodeCallback is idempotent so repeated calls are safe.
872872
*/
873873
private async ensureZooGatewayProfileSeeded(): Promise<void> {
874-
const { getCachedZooCodeToken } = await import("../../services/zoo-code-auth")
874+
const { getCachedZooCodeToken, getZooCodeBaseUrl } = await import("../../services/zoo-code-auth")
875875
const token = getCachedZooCodeToken()
876876
if (!token) return
877+
const expectedGatewayBaseUrl = `${getZooCodeBaseUrl()}/api/gateway/v1`
877878

878879
// Check ALL zoo-gateway profiles — only skip seeding if every profile has the current token.
879880
// Using .find() would miss stale tokens in duplicate/renamed profiles since handleZooCodeCallback
@@ -889,13 +890,12 @@ export class ClineProvider
889890
for (const entry of zooGatewayProfiles) {
890891
try {
891892
const fullProfile = await this.providerSettingsManager.getProfile({ name: entry.name })
892-
if (fullProfile.zooSessionToken !== token) {
893+
if (
894+
fullProfile.zooSessionToken !== token ||
895+
fullProfile.zooGatewayBaseUrl !== expectedGatewayBaseUrl
896+
) {
893897
allUpToDate = false
894-
this.log(
895-
fullProfile.zooSessionToken
896-
? "[ensureZooGatewayProfileSeeded] Token mismatch (stale session?), updating with current token"
897-
: "[ensureZooGatewayProfileSeeded] Existing zoo-gateway profile has no token, updating with cached token",
898-
)
898+
this.log("[ensureZooGatewayProfileSeeded] Existing zoo-gateway profile is stale, updating")
899899
break
900900
}
901901
} catch {

src/core/webview/webviewMessageHandler.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -955,13 +955,16 @@ export const webviewMessageHandler = async (
955955
if (!zooGatewayToken) {
956956
try {
957957
const allProfiles = await provider.providerSettingsManager.listConfig()
958-
const zooGatewayProfile = allProfiles.find((p) => p.apiProvider === "zoo-gateway")
959-
if (zooGatewayProfile) {
958+
const zooGatewayProfiles = allProfiles.filter((p) => p.apiProvider === "zoo-gateway")
959+
for (const profileMeta of zooGatewayProfiles) {
960960
const fullProfile = await provider.providerSettingsManager.getProfile({
961-
name: zooGatewayProfile.name,
961+
name: profileMeta.name,
962962
})
963-
zooGatewayToken = fullProfile.zooSessionToken
964-
zooGatewayBaseUrl = fullProfile.zooGatewayBaseUrl ?? zooGatewayBaseUrl
963+
if (fullProfile.zooSessionToken) {
964+
zooGatewayToken = fullProfile.zooSessionToken
965+
zooGatewayBaseUrl = fullProfile.zooGatewayBaseUrl ?? zooGatewayBaseUrl
966+
break
967+
}
965968
}
966969
} catch (error) {
967970
console.debug("Failed to look up zoo-gateway profile for model fetch:", error)

webview-ui/src/components/welcome/WelcomeViewProvider.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,17 @@ const DEFAULT_WELCOME_API_CONFIGURATION: ProviderSettings = {
2020
openRouterModelId: openRouterDefaultModelId,
2121
}
2222

23-
const getWelcomeApiConfiguration = (apiConfiguration?: ProviderSettings): ProviderSettings => {
23+
const getWelcomeApiConfiguration = (
24+
apiConfiguration?: ProviderSettings,
25+
zooCodeIsAuthenticated?: boolean,
26+
): ProviderSettings => {
2427
// validateApiConfiguration treats a missing apiProvider as valid (no switch case matches),
2528
// so we explicitly fall back here before delegating to it for incomplete-but-set configs.
2629
if (!apiConfiguration?.apiProvider) {
2730
return DEFAULT_WELCOME_API_CONFIGURATION
2831
}
2932

30-
const validationError = validateApiConfiguration(apiConfiguration)
33+
const validationError = validateApiConfiguration(apiConfiguration, undefined, undefined, zooCodeIsAuthenticated)
3134
if (validationError) {
3235
return DEFAULT_WELCOME_API_CONFIGURATION
3336
}
@@ -42,7 +45,8 @@ const WelcomeViewProvider = () => {
4245
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined)
4346
const [showProviderSetup, setShowProviderSetup] = useState(false)
4447
const [welcomeApiConfiguration, setWelcomeApiConfiguration] = useState<ProviderSettings>()
45-
const effectiveApiConfiguration = welcomeApiConfiguration ?? getWelcomeApiConfiguration(apiConfiguration)
48+
const effectiveApiConfiguration =
49+
welcomeApiConfiguration ?? getWelcomeApiConfiguration(apiConfiguration, zooCodeIsAuthenticated)
4650

4751
const setApiConfigurationFieldForApiOptions = useCallback(
4852
<K extends keyof ProviderSettings>(field: K, value: ProviderSettings[K]) => {
@@ -57,7 +61,7 @@ const WelcomeViewProvider = () => {
5761

5862
const handleGetStarted = useCallback(() => {
5963
if (!showProviderSetup) {
60-
const initialApiConfiguration = getWelcomeApiConfiguration(apiConfiguration)
64+
const initialApiConfiguration = getWelcomeApiConfiguration(apiConfiguration, zooCodeIsAuthenticated)
6165
setWelcomeApiConfiguration(initialApiConfiguration)
6266

6367
setApiConfiguration(initialApiConfiguration)

0 commit comments

Comments
 (0)