Просмотр исходного кода

fix(studio): 保存模型服务时不自动测试连接

fanghanjun 6 месяцев назад
Родитель
Сommit
f9d6079b3f

+ 2 - 5
packages/studio/src/pages/ServiceDetailPage.tsx

@@ -6,7 +6,7 @@ import {
   matchServiceConfigEntryForDetail,
   probeServiceForDetail,
   rehydrateServiceConnectionStatus,
-  saveServiceConfigWithValidation,
+  saveServiceConfig,
   type ServiceDetailConnectionStatus as ConnectionStatus,
   type ServiceDetailDetectedConfig as DetectedConfig,
   type ServiceDetailModelInfo as ModelInfo,
@@ -169,7 +169,7 @@ export function ServiceDetailPage({ serviceId, nav }: { serviceId: string; nav:
     }
     setStatus({ state: "saving" });
     try {
-      const result = await saveServiceConfigWithValidation({
+      const result = await saveServiceConfig({
         effectiveServiceId,
         serviceId,
         isCustom,
@@ -190,9 +190,6 @@ export function ServiceDetailPage({ serviceId, nav }: { serviceId: string; nav:
         setStoreModels(effectiveServiceId, result.status.models);
         setStatus(result.status);
       } else {
-        clearStoreModels(effectiveServiceId);
-        setDetectedModel("");
-        setDetectedConfig(null);
         setStatus(result.status);
       }
       await refreshServices();

+ 28 - 27
packages/studio/src/pages/service-detail-state.test.ts

@@ -2,7 +2,7 @@ import { describe, expect, it, vi } from "vitest";
 import {
   matchServiceConfigEntryForDetail,
   rehydrateServiceConnectionStatus,
-  saveServiceConfigWithValidation,
+  saveServiceConfig,
 } from "./service-detail-state";
 
 describe("rehydrateServiceConnectionStatus", () => {
@@ -52,25 +52,19 @@ describe("matchServiceConfigEntryForDetail", () => {
   });
 });
 
-describe("saveServiceConfigWithValidation", () => {
-  it("validates the key before persisting secrets/config", async () => {
+describe("saveServiceConfig", () => {
+  it("persists secrets/config without probing the upstream service", async () => {
     const calls: string[] = [];
-    const fetchJsonImpl = vi.fn(async (path: string) => {
+    const bodies: unknown[] = [];
+    const fetchJsonImpl = vi.fn(async (path: string, init?: { body?: string }) => {
       calls.push(path);
-      if (path === "/services/openai/test") {
-        return {
-          ok: true,
-          models: [{ id: "gpt-5.4", name: "gpt-5.4" }],
-          selectedModel: "gpt-5.4",
-          detected: { apiFormat: "responses", stream: false },
-        };
-      }
+      if (init?.body) bodies.push(JSON.parse(init.body));
       if (path === "/services/openai/secret") return { ok: true };
       if (path === "/services/config") return { ok: true };
       throw new Error(`unexpected path: ${path}`);
     });
 
-    const result = await saveServiceConfigWithValidation({
+    const result = await saveServiceConfig({
       effectiveServiceId: "openai",
       serviceId: "openai",
       isCustom: false,
@@ -85,31 +79,35 @@ describe("saveServiceConfigWithValidation", () => {
     });
 
     expect(calls).toEqual([
-      "/services/openai/test",
       "/services/openai/secret",
       "/services/config",
     ]);
-    expect(result).toMatchObject({
-      detectedModel: "gpt-5.4",
-      detectedConfig: { apiFormat: "responses", stream: false },
-      status: {
-        state: "connected",
-        models: [{ id: "gpt-5.4", name: "gpt-5.4" }],
+    expect(bodies).toEqual([
+      { apiKey: "sk-live" },
+      {
+        service: "openai",
+        services: [
+          { service: "openai", temperature: 0.7, apiFormat: "chat", stream: true },
+        ],
       },
+    ]);
+    expect(result).toEqual({
+      detectedModel: "",
+      detectedConfig: null,
+      status: { state: "saved" },
     });
   });
 
-  it("does not persist secrets/config when validation fails", async () => {
+  it("still persists secrets/config when the key has not been manually tested", async () => {
     const calls: string[] = [];
     const fetchJsonImpl = vi.fn(async (path: string) => {
       calls.push(path);
-      if (path === "/services/openai/test") {
-        throw new Error("401 Unauthorized");
-      }
+      if (path === "/services/openai/secret") return { ok: true };
+      if (path === "/services/config") return { ok: true };
       throw new Error(`unexpected path: ${path}`);
     });
 
-    await expect(saveServiceConfigWithValidation({
+    await expect(saveServiceConfig({
       effectiveServiceId: "openai",
       serviceId: "openai",
       isCustom: false,
@@ -121,8 +119,11 @@ describe("saveServiceConfigWithValidation", () => {
       temperature: "0.7",
       detectedModel: "",
       fetchJsonImpl: fetchJsonImpl as never,
-    })).rejects.toThrow("401 Unauthorized");
+    })).resolves.toMatchObject({ status: { state: "saved" } });
 
-    expect(calls).toEqual(["/services/openai/test"]);
+    expect(calls).toEqual([
+      "/services/openai/secret",
+      "/services/config",
+    ]);
   });
 });

+ 8 - 30
packages/studio/src/pages/service-detail-state.ts

@@ -93,7 +93,7 @@ export function matchServiceConfigEntryForDetail(
   });
 }
 
-export async function saveServiceConfigWithValidation(args: {
+export async function saveServiceConfig(args: {
   readonly effectiveServiceId: string;
   readonly serviceId: string;
   readonly isCustom: boolean;
@@ -114,20 +114,6 @@ export async function saveServiceConfigWithValidation(args: {
   const trimmedKey = args.apiKey.trim();
   const trimmedBaseUrl = args.baseUrl.trim();
 
-  let probeResult: ServiceProbeResponse | null = null;
-  if (trimmedKey) {
-    probeResult = await probeServiceForDetail(
-      args.effectiveServiceId,
-      {
-        apiKey: trimmedKey,
-        apiFormat: args.apiFormat,
-        stream: args.stream,
-        ...(args.isCustom ? { baseUrl: trimmedBaseUrl } : {}),
-      },
-      { fetchJsonImpl },
-    );
-  }
-
   await fetchJsonImpl(`/services/${encodeURIComponent(args.effectiveServiceId)}/secret`, {
     method: "PUT",
     headers: { "Content-Type": "application/json" },
@@ -139,33 +125,25 @@ export async function saveServiceConfigWithValidation(args: {
     headers: { "Content-Type": "application/json" },
     body: JSON.stringify({
       service: args.effectiveServiceId,
-      ...(probeResult?.selectedModel ? { defaultModel: probeResult.selectedModel } : args.detectedModel ? { defaultModel: args.detectedModel } : {}),
+      ...(args.detectedModel ? { defaultModel: args.detectedModel } : {}),
       services: [
         {
           service: args.isCustom ? "custom" : args.serviceId,
           temperature: parseFloat(args.temperature),
-          apiFormat: probeResult?.detected?.apiFormat ?? args.apiFormat,
-          stream: typeof probeResult?.detected?.stream === "boolean" ? probeResult.detected.stream : args.stream,
+          apiFormat: args.apiFormat,
+          stream: args.stream,
           ...(args.isCustom ? {
             name: args.resolvedCustomName,
-            baseUrl: probeResult?.detected?.baseUrl ?? trimmedBaseUrl,
+            baseUrl: trimmedBaseUrl,
           } : {}),
         },
       ],
     }),
   });
 
-  if (!probeResult) {
-    return {
-      status: { state: "saved" },
-      detectedModel: "",
-      detectedConfig: null,
-    };
-  }
-
   return {
-    status: { state: "connected", models: probeResult.models ?? [] },
-    detectedModel: probeResult.selectedModel ?? "",
-    detectedConfig: probeResult.detected ?? null,
+    status: { state: "saved" },
+    detectedModel: "",
+    detectedConfig: null,
   };
 }