fix(proxy): keep the routing service running for Claude Desktop model mapping

Claude Desktop model-mapping providers send every request through the local
routing service, but the service lifecycle only counted the four routing apps:
when Claude Code left routing, stop_server_if_unused stopped the service Desktop
was still using, and nothing started it on launch.

- stop_server_if_unused now also treats a current Desktop model-mapping
  provider as a user of the service.
- ensure_desktop_mapping_service starts the service when that provider is in
  use; it runs on startup, after switching a Desktop provider and after
  applying a project that covers Claude Desktop.
- The frontend no longer warns that routing must be started when switching to
  a Desktop model-mapping provider, since the backend starts it; drop the now
  unused isProxyRunning hook argument and proxyReasonClaudeDesktop string.
This commit is contained in:
Jason committed 2026-10-03 23:28:58 +08:00
1 parent cee903476d
commit 98798552c0
11 files changed
+148 -91

No files matched your search

+39 -54
View File
@@ -233,7 +233,7 @@ describe("useProviderActions", () => {
},
});
const { result } = renderHook(() => useProviderActions("claude", false), {
const { result } = renderHook(() => useProviderActions("claude"), {
wrapper,
});
@@ -255,7 +255,7 @@ describe("useProviderActions", () => {
},
});
const { result } = renderHook(() => useProviderActions("codex", false), {
const { result } = renderHook(() => useProviderActions("codex"), {
wrapper,
});
@@ -275,7 +275,7 @@ describe("useProviderActions", () => {
meta: { apiFormat: "anthropic" },
});
const { result } = renderHook(() => useProviderActions("codex", false), {
const { result } = renderHook(() => useProviderActions("codex"), {
wrapper,
});
@@ -310,10 +310,9 @@ describe("useProviderActions", () => {
}),
];
const { result } = renderHook(
() => useProviderActions("grokbuild", false),
{ wrapper },
);
const { result } = renderHook(() => useProviderActions("grokbuild"), {
wrapper,
});
for (const provider of providers) {
await act(async () => {
@@ -336,10 +335,9 @@ describe("useProviderActions", () => {
},
});
const { result } = renderHook(
() => useProviderActions("codex", true, false),
{ wrapper },
);
const { result } = renderHook(() => useProviderActions("codex", false), {
wrapper,
});
await act(async () => {
await result.current.switchProvider(provider);
@@ -362,8 +360,28 @@ describe("useProviderActions", () => {
},
});
const { result } = renderHook(() => useProviderActions("codex", true), {
wrapper,
});
await act(async () => {
await result.current.switchProvider(provider);
});
expect(toastWarningMock).not.toHaveBeenCalled();
expect(switchProviderMutateAsync).toHaveBeenCalledWith(provider.id);
});
it("does not warn for Claude Desktop model mapping, which starts routing itself", async () => {
switchProviderMutateAsync.mockResolvedValue(undefined);
const { wrapper } = createWrapper();
const provider = createProvider({
category: "custom",
meta: { claudeDesktopMode: "proxy" },
});
const { result } = renderHook(
() => useProviderActions("codex", true, true),
() => useProviderActions("claude-desktop", false),
{ wrapper },
);
@@ -375,36 +393,6 @@ describe("useProviderActions", () => {
expect(switchProviderMutateAsync).toHaveBeenCalledWith(provider.id);
});
it("uses proxy process readiness for Claude Desktop routing", async () => {
switchProviderMutateAsync.mockResolvedValue(undefined);
const { wrapper } = createWrapper();
const provider = createProvider({
category: "custom",
meta: { claudeDesktopMode: "proxy" },
});
const { result, rerender } = renderHook(
({ isProxyRunning }) =>
useProviderActions("claude-desktop", isProxyRunning, false),
{ initialProps: { isProxyRunning: true }, wrapper },
);
await act(async () => {
await result.current.switchProvider(provider);
});
expect(toastWarningMock).not.toHaveBeenCalled();
rerender({ isProxyRunning: false });
await act(async () => {
await result.current.switchProvider(provider);
});
expect(toastWarningMock).toHaveBeenCalledTimes(1);
expect(toastWarningMock).toHaveBeenCalledWith(
expect.stringContaining("Claude Desktop 本地路由模式"),
);
});
it("allows the native Codex official provider during takeover", async () => {
switchProviderMutateAsync.mockResolvedValueOnce(undefined);
const { wrapper } = createWrapper();
@@ -413,10 +401,9 @@ describe("useProviderActions", () => {
category: "official",
});
const { result } = renderHook(
() => useProviderActions("codex", true, true),
{ wrapper },
);
const { result } = renderHook(() => useProviderActions("codex", true), {
wrapper,
});
await act(async () => {
await result.current.switchProvider(provider);
@@ -433,10 +420,9 @@ describe("useProviderActions", () => {
category: "official",
});
const { result } = renderHook(
() => useProviderActions("claude", true, true),
{ wrapper },
);
const { result } = renderHook(() => useProviderActions("claude", true), {
wrapper,
});
await act(async () => {
await result.current.switchProvider(provider);
@@ -462,10 +448,9 @@ describe("useProviderActions", () => {
},
},
});
const { result } = renderHook(
() => useProviderActions("codex", true, true),
{ wrapper },
);
const { result } = renderHook(() => useProviderActions("codex", true), {
wrapper,
});
await act(async () => {
await result.current.switchProvider(provider);