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

refactor(core): inline pi-ai provider resolution, drop adapter file

抽 provider-to-pi-ai.ts adapter 的收益低(只有 5 行 if-else 逻辑 + 一个
小映射表),不值得单独一个文件 + 独立测试文件。inline 到 createLLMClient
更符合 YAGNI。

- 删 packages/core/src/llm/providers/provider-to-pi-ai.ts
- 删 packages/core/src/__tests__/provider-to-pi-ai.test.ts
- 5 行 if-else inline 到 createLLMClient
- 保留 providers-schema.test.ts 的 'endpoint 无 piProvider 字段' 防回归断言

832 tests 全绿。
fanghanjun 6 месяцев назад
Родитель
Сommit
1dada1f0b1

+ 2 - 2
CHANGELOG.md

@@ -49,14 +49,14 @@
 
 - **目录分层**:42 个 endpoint 文件从 `providers/*.ts` 下沉到 `providers/endpoints/*.ts`;`types.ts` / `lookup.ts` / `verify.ts` / `provider-to-pi-ai.ts` / `index.ts` 留在 `providers/` 根
 - **类型重命名**:`InkosProvider` → `InkosEndpoint`;`getProvider` / `getAllProviders` → `getEndpoint` / `getAllEndpoints`(对齐"endpoint = service"语义)
-- **pi-ai adapter 抽出**:新增 `provider-to-pi-ai.ts`,endpoint 文件不再声明 `piProvider` 字段;adapter 按 `endpoint.id + endpoint.api` 推导 pi-ai 的 `provider` 字段(显式映射:`zhipu → zai` / `openrouter` / `githubCopilot`;`anthropic-messages` → `anthropic`;其余默认 `openai` 让 pi-ai 再按 baseUrl 嗅探)
+- **pi-ai provider 字段推导 inline 到 `createLLMClient`**:endpoint 文件不再声明 `piProvider` 字段;`createLLMClient` 里 5 行 if-else 按 `endpoint.id + endpoint.api` 推出 pi-ai 的 `provider` 字段(显式映射:`zhipu → zai` / `openrouter` / `githubCopilot`;`anthropic-messages` → `anthropic`;其余默认让 pi-ai 按 baseUrl 自动嗅探)
 - **架构定位澄清**:inkos provider 层定位为 **pi-ai 的上游元数据 + 业务层**(model 精确元数据、写作温度推荐、endpoint 选择),不替代 pi-ai 的 transport 职责
 - **Tier 1 官方文档校对**:12 个主流 provider(anthropic / openai / google / deepseek / qwen / minimax / moonshot / zhipu / siliconcloud / ppio / bailian / volcengine)按官方文档核对,发现并修正:
   - `qwen`:checkModel `qwen-turbo` → `qwen-flash`(qwen-turbo 官方已停更);`qwen-turbo.maxOutput` 16384 → 8192;`qwen-max.contextWindowTokens` 131072 → 32768
   - `minimax`:`temperatureRange` [0,2] → [0,1](MiniMax Anthropic 兼容端官方上限是 1)
   - `zhipu`:checkModel `glm-4-flash` → `glm-4.7-flash`(glm-4-flash 不在当前 models 清单,会 404)
   - `volcengine`:`temperatureRange` [0,1] → [0,2](火山方舟 OpenAI 兼容协议实际是 [0,2]);`doubao-seed-2.0-{pro,lite,mini,code}.maxOutput` 128000 → 32000;`glm-4-7.maxOutput` 128000 → 16000
-- **新增测试**:`provider-to-pi-ai.test.ts` adapter 单元测试(覆盖 12+ provider 映射预期);`providers-schema.test.ts` 补 "endpoint 无 piProvider 字段" 防回归断言
+- **新增测试**:`providers-schema.test.ts` 补 "endpoint 无 piProvider 字段" 防回归断言(pi-ai provider 字段推导的正确性由 `provider.test.ts` 的 createLLMClient 回归测试覆盖)
 
 ---
 

+ 0 - 54
packages/core/src/__tests__/provider-to-pi-ai.test.ts

@@ -1,54 +0,0 @@
-import { describe, it, expect } from "vitest";
-import { resolvePiAiProvider } from "../llm/providers/provider-to-pi-ai.js";
-import { getEndpoint } from "../llm/providers/index.js";
-
-describe("resolvePiAiProvider (R3)", () => {
-  it("zhipu 显式映射到 zai(pi-ai 识别不到 open.bigmodel.cn)", () => {
-    const ep = getEndpoint("zhipu");
-    expect(ep).toBeDefined();
-    expect(resolvePiAiProvider(ep!)).toBe("zai");
-  });
-
-  it("openrouter 显式映射到 openrouter", () => {
-    const ep = getEndpoint("openrouter");
-    expect(ep).toBeDefined();
-    expect(resolvePiAiProvider(ep!)).toBe("openrouter");
-  });
-
-  it("githubCopilot 显式映射到 githubCopilot", () => {
-    const ep = getEndpoint("githubCopilot");
-    expect(ep).toBeDefined();
-    expect(resolvePiAiProvider(ep!)).toBe("githubCopilot");
-  });
-
-  it("anthropic-messages 协议统一映射到 anthropic(anthropic / minimax / bailian / xxxCodingPlan)", () => {
-    for (const id of [
-      "anthropic",
-      "minimax",
-      "bailian",
-      "kimiCodingPlan",
-      "minimaxCodingPlan",
-      "bailianCodingPlan",
-      "glmCodingPlan",
-      "volcengineCodingPlan",
-      "opencodeCodingPlan",
-    ]) {
-      const ep = getEndpoint(id);
-      expect(ep, `${id}`).toBeDefined();
-      expect(resolvePiAiProvider(ep!), `${id}`).toBe("anthropic");
-    }
-  });
-
-  it("其余 OpenAI 兼容默认映射到 openai(pi-ai 再按 baseUrl 嗅探 deepseek/xai/ollama 等)", () => {
-    for (const id of ["deepseek", "qwen", "moonshot", "siliconcloud", "ppio", "mistral", "xai", "ollama", "google"]) {
-      const ep = getEndpoint(id);
-      expect(ep, `${id}`).toBeDefined();
-      expect(resolvePiAiProvider(ep!), `${id}`).toBe("openai");
-    }
-  });
-
-  it("openai-responses 默认映射到 openai(OpenAI 官方)", () => {
-    const ep = getEndpoint("openai");
-    expect(resolvePiAiProvider(ep!)).toBe("openai");
-  });
-});

+ 8 - 4
packages/core/src/llm/provider.ts

@@ -17,7 +17,6 @@ import type {
 import { resolveServicePreset } from "./service-presets.js";
 import { getEndpoint } from "./providers/index.js";
 import { lookupModel } from "./providers/lookup.js";
-import { resolvePiAiProvider } from "./providers/provider-to-pi-ai.js";
 
 
 // === Streaming Monitor Types ===
@@ -158,9 +157,14 @@ export function createLLMClient(config: LLMConfig): LLMClient {
   const extraHeaders = config.headers ?? parseEnvHeaders();
 
   const provider = config.provider === "anthropic" ? "anthropic" : "openai";
-  const piProvider = inkosProvider
-    ? resolvePiAiProvider(inkosProvider)
-    : provider;
+  // 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";
+  else if (inkosProvider?.id === "openrouter") piProvider = "openrouter";
+  else if (inkosProvider?.id === "githubCopilot") piProvider = "githubCopilot";
+  else if (inkosProvider?.api === "anthropic-messages") piProvider = "anthropic";
+  else piProvider = provider;
 
   const piModel: PiModel<PiApi> = {
     id: modelCard?.deploymentName ?? config.model,

+ 0 - 24
packages/core/src/llm/providers/provider-to-pi-ai.ts

@@ -1,24 +0,0 @@
-import type { InkosEndpoint } from "./types.js";
-
-/**
- * 从 InkosEndpoint 推出 pi-ai Model 的 `provider` 字段。
- *
- * pi-ai 内部会再根据 baseUrl 做 per-vendor 嗅探(api.z.ai / api.x.ai /
- * openrouter.ai / deepseek.com / api.anthropic.com 等),所以大多数情况下
- * 这里返回 "openai" 或 "anthropic" 即可。
- *
- * 少数 pi-ai 嗅探不到的需要显式覆盖(如智谱的 open.bigmodel.cn 不含 z.ai 域名,
- * 但要走 zai 兼容层)。
- */
-export function resolvePiAiProvider(endpoint: InkosEndpoint): string {
-  const explicitMap: Record<string, string> = {
-    zhipu: "zai",
-    openrouter: "openrouter",
-    githubCopilot: "githubCopilot",
-  };
-  if (endpoint.id in explicitMap) return explicitMap[endpoint.id]!;
-
-  if (endpoint.api === "anthropic-messages") return "anthropic";
-
-  return "openai";
-}