Преглед на файлове

fix: use native google provider

fanghanjun преди 6 месеца
родител
ревизия
3297f3e703

+ 1 - 1
packages/core/src/__tests__/config-loader.test.ts

@@ -280,7 +280,7 @@ describe("loadProjectConfig local provider auth", () => {
     expect(config.llm.configSource).toBe("studio");
     expect(config.llm.service).toBe("google");
     expect(config.llm.provider).toBe("openai");
-    expect(config.llm.baseUrl).toBe("https://generativelanguage.googleapis.com/v1beta/openai");
+    expect(config.llm.baseUrl).toBe("https://generativelanguage.googleapis.com/v1beta");
     expect(config.llm.model).toBe("gemini-2.5-flash");
     expect(config.llm.apiKey).toBe("sk-google");
   });

+ 3 - 3
packages/core/src/__tests__/effective-llm-config.test.ts

@@ -50,7 +50,7 @@ describe("resolveEffectiveLLMConfig", () => {
     expect(result.llm.configSource).toBe("studio");
     expect(result.diagnostics.configMode).toBe("studio-project");
     expect(result.llm.service).toBe("google");
-    expect(result.llm.baseUrl).toBe("https://generativelanguage.googleapis.com/v1beta/openai");
+    expect(result.llm.baseUrl).toBe("https://generativelanguage.googleapis.com/v1beta");
     expect(result.llm.model).toBe("gemini-2.5-flash");
     expect(result.llm.apiKey).toBe("sk-google");
     expect(result.diagnostics.apiKeySource).toBe("studio-secret");
@@ -226,7 +226,7 @@ describe("resolveEffectiveLLMConfig", () => {
     expect(result.diagnostics.configMode).toBe("legacy-env");
     expect(result.llm.service).toBe("google");
     expect(result.llm.provider).toBe("openai");
-    expect(result.llm.baseUrl).toBe("https://generativelanguage.googleapis.com/v1beta/openai");
+    expect(result.llm.baseUrl).toBe("https://generativelanguage.googleapis.com/v1beta");
     expect(result.llm.apiFormat).toBe("chat");
     expect(result.llm.model).toBe("gemini-2.5-flash");
     expect(result.llm.apiKey).toBe("sk-google");
@@ -346,7 +346,7 @@ describe("resolveEffectiveLLMConfig", () => {
 
     expect(result.llm.service).toBe("google");
     expect(result.llm.provider).toBe("openai");
-    expect(result.llm.baseUrl).toBe("https://generativelanguage.googleapis.com/v1beta/openai");
+    expect(result.llm.baseUrl).toBe("https://generativelanguage.googleapis.com/v1beta");
     expect(result.llm.model).toBe("gemini-2.5-flash");
     expect(result.llm.apiKey).toBe("sk-google");
     expect(result.diagnostics.serviceSource).toBe("cli");

+ 6 - 3
packages/core/src/__tests__/provider.test.ts

@@ -594,7 +594,7 @@ describe("createLLMClient with providers lookup", () => {
     expect(client._piModel?.id).toBe("kimi-k2-thinking");
   });
 
-  it("Google Gemini OpenAI-compatible endpoint disables OpenAI store parameter", async () => {
+  it("Google Gemini uses native google-generative-ai provider", async () => {
     const { createLLMClient } = await import("../llm/provider.js");
     const { LLMConfigSchema } = await import("../models/project.js");
     const client = createLLMClient(LLMConfigSchema.parse({
@@ -602,8 +602,11 @@ describe("createLLMClient with providers lookup", () => {
       service: "google",
       model: "gemini-2.5-flash",
       apiKey: "test",
-      baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai",
+      baseUrl: "https://generativelanguage.googleapis.com/v1beta",
     }));
-    expect(client._piModel?.compat).toMatchObject({ supportsStore: false });
+    expect(client._piModel?.api).toBe("google-generative-ai");
+    expect(client._piModel?.provider).toBe("google");
+    expect(client._piModel?.baseUrl).toBe("https://generativelanguage.googleapis.com/v1beta");
+    expect(client._piModel?.compat).toBeUndefined();
   });
 });

+ 5 - 7
packages/core/src/__tests__/service-resolver.test.ts

@@ -84,7 +84,7 @@ describe("resolveServiceModel", () => {
     expect(result.temperatureRange).toEqual([0, 2]);
   });
 
-  it("preserves Google OpenAI-compatible overrides on resolved model", async () => {
+  it("resolves Google to native google-generative-ai model", async () => {
     await mkdir(join(root, ".inkos"), { recursive: true });
     await writeFile(
       join(root, ".inkos", "secrets.json"),
@@ -93,12 +93,10 @@ describe("resolveServiceModel", () => {
 
     const result = await resolveServiceModel("google", "gemini-pro-latest", root);
 
-    expect(result.model.api).toBe("openai-completions");
-    expect(result.model.baseUrl).toBe("https://generativelanguage.googleapis.com/v1beta/openai");
-    expect(result.model.compat).toMatchObject({
-      supportsStore: false,
-      requiresAssistantAfterToolResult: true,
-    });
+    expect(result.model.api).toBe("google-generative-ai");
+    expect(result.model.provider).toBe("google");
+    expect(result.model.baseUrl).toBe("https://generativelanguage.googleapis.com/v1beta");
+    expect(result.model.compat).toBeUndefined();
   });
 
   it("preserves DeepSeek tool-result bridge compatibility on resolved model", async () => {

+ 5 - 2
packages/core/src/llm/provider.ts

@@ -167,13 +167,16 @@ export function createLLMClient(config: LLMConfig): LLMClient {
   const piApi = resolvePiApi(serviceName, config.apiFormat, (inkosProvider?.api ?? preset?.api) as PiApi) as PiApi;
   const baseUrl = config.baseUrl || inkosProvider?.baseUrl || preset?.baseUrl || "";
   const extraHeaders = config.headers ?? parseEnvHeaders();
-  const compat = resolveProviderCompat(inkosProvider, baseUrl);
+  const compat = piApi === "openai-completions"
+    ? resolveProviderCompat(inkosProvider, baseUrl)
+    : undefined;
 
   const provider = config.provider === "anthropic" ? "anthropic" : "openai";
   // pi-ai provider 字段:大多数情况 pi-ai 会按 baseUrl 自动嗅探(openrouter.ai / api.z.ai /
   // api.x.ai / deepseek.com / anthropic.com 等)。这里只列 pi-ai 嗅探不到、需要显式指定的少数情况。
   let piProvider: string;
-  if (inkosProvider?.id === "zhipu") piProvider = "zai";
+  if (inkosProvider?.id === "google") piProvider = "google";
+  else if (inkosProvider?.id === "zhipu") piProvider = "zai";
   else if (inkosProvider?.id === "openrouter") piProvider = "openrouter";
   else if (inkosProvider?.id === "githubCopilot") piProvider = "githubCopilot";
   else if (inkosProvider?.api === "anthropic-messages") piProvider = "anthropic";

+ 3 - 3
packages/core/src/llm/providers/endpoints/google.ts

@@ -13,10 +13,10 @@ export const GOOGLE: InkosEndpoint = {
   id: "google",
   label: "Google Gemini",
   group: "overseas",
-  api: "openai-completions",
-  baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai",
+  api: "google-generative-ai",
+  baseUrl: "https://generativelanguage.googleapis.com/v1beta",
+  modelsBaseUrl: "https://generativelanguage.googleapis.com/v1beta/openai",
   checkModel: "gemini-2.5-flash",
-  compat: { supportsStore: false, requiresAssistantAfterToolResult: true },
   temperatureRange: [0, 2],
   defaultTemperature: 1,
   writingTemperature: 1,

+ 2 - 1
packages/core/src/llm/providers/types.ts

@@ -9,7 +9,8 @@
 export type ApiProtocol =
   | "openai-completions"
   | "openai-responses"
-  | "anthropic-messages";
+  | "anthropic-messages"
+  | "google-generative-ai";
 
 export type EndpointGroup =
   | "overseas"

+ 2 - 0
packages/core/src/llm/service-presets.ts

@@ -85,6 +85,7 @@ export function resolveServiceProviderFamily(service: string): "openai" | "anthr
 }
 
 export function resolveServicePiProvider(service: string): string | undefined {
+  if (service === "google") return "google";
   const preset = resolveServicePreset(service);
   if (!preset) return undefined;
   return preset.piProvider ?? preset.providerFamily;
@@ -126,6 +127,7 @@ export const SERVICE_TO_PI_PROVIDER: Record<string, string> = Object.fromEntries
     .filter(([service]) => service !== "custom")
     .map(([service, preset]) => [service, preset.piProvider ?? preset.providerFamily]),
 ) as Record<string, string>;
+SERVICE_TO_PI_PROVIDER.google = "google";
 
 export interface ModelInfo {
   readonly id: string;