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

feat(core): add provider-to-pi-ai adapter, replace inline piProvider resolution

- 新增 packages/core/src/llm/providers/provider-to-pi-ai.ts 提供 resolvePiAiProvider(endpoint)
- 显式映射表:zhipu → zai(pi-ai 识别不到 open.bigmodel.cn),
  openrouter → openrouter, githubCopilot → githubCopilot
- anthropic-messages 协议统一映射到 anthropic;其余默认 openai(pi-ai 再按 baseUrl 嗅探)
- createLLMClient 里 piModel.provider 字段改调 resolvePiAiProvider
- 其他 Provider/getProvider refs 批量 rename 到 Endpoint/getEndpoint(补齐 R1-2 遗漏)

R1-3,测试 831 全绿。
fanghanjun 6 месяцев назад
Родитель
Сommit
9d63801367
50 измененных файлов с 155 добавлено и 127 удалено
  1. 22 22
      packages/core/src/__tests__/providers-schema.test.ts
  2. 7 3
      packages/core/src/llm/provider.ts
  3. 2 2
      packages/core/src/llm/providers/endpoints/ai360.ts
  4. 2 2
      packages/core/src/llm/providers/endpoints/anthropic.ts
  5. 2 2
      packages/core/src/llm/providers/endpoints/baichuan.ts
  6. 2 2
      packages/core/src/llm/providers/endpoints/bailian.ts
  7. 2 2
      packages/core/src/llm/providers/endpoints/bailianCodingPlan.ts
  8. 2 2
      packages/core/src/llm/providers/endpoints/custom.ts
  9. 2 2
      packages/core/src/llm/providers/endpoints/deepseek.ts
  10. 2 2
      packages/core/src/llm/providers/endpoints/giteeai.ts
  11. 2 2
      packages/core/src/llm/providers/endpoints/githubCopilot.ts
  12. 2 2
      packages/core/src/llm/providers/endpoints/glmCodingPlan.ts
  13. 2 2
      packages/core/src/llm/providers/endpoints/google.ts
  14. 2 2
      packages/core/src/llm/providers/endpoints/higress.ts
  15. 2 2
      packages/core/src/llm/providers/endpoints/hunyuan.ts
  16. 2 2
      packages/core/src/llm/providers/endpoints/infiniai.ts
  17. 2 2
      packages/core/src/llm/providers/endpoints/internlm.ts
  18. 2 2
      packages/core/src/llm/providers/endpoints/kimiCodingPlan.ts
  19. 2 2
      packages/core/src/llm/providers/endpoints/longcat.ts
  20. 2 2
      packages/core/src/llm/providers/endpoints/minimax.ts
  21. 2 2
      packages/core/src/llm/providers/endpoints/minimaxCodingPlan.ts
  22. 2 2
      packages/core/src/llm/providers/endpoints/mistral.ts
  23. 2 2
      packages/core/src/llm/providers/endpoints/modelscope.ts
  24. 2 2
      packages/core/src/llm/providers/endpoints/moonshot.ts
  25. 2 2
      packages/core/src/llm/providers/endpoints/newapi.ts
  26. 2 2
      packages/core/src/llm/providers/endpoints/ollama.ts
  27. 2 2
      packages/core/src/llm/providers/endpoints/openai.ts
  28. 2 2
      packages/core/src/llm/providers/endpoints/opencodeCodingPlan.ts
  29. 2 2
      packages/core/src/llm/providers/endpoints/openrouter.ts
  30. 2 2
      packages/core/src/llm/providers/endpoints/ppio.ts
  31. 2 2
      packages/core/src/llm/providers/endpoints/qiniu.ts
  32. 2 2
      packages/core/src/llm/providers/endpoints/qwen.ts
  33. 2 2
      packages/core/src/llm/providers/endpoints/sensenova.ts
  34. 2 2
      packages/core/src/llm/providers/endpoints/siliconcloud.ts
  35. 2 2
      packages/core/src/llm/providers/endpoints/spark.ts
  36. 2 2
      packages/core/src/llm/providers/endpoints/stepfun.ts
  37. 2 2
      packages/core/src/llm/providers/endpoints/tencentcloud.ts
  38. 2 2
      packages/core/src/llm/providers/endpoints/volcengine.ts
  39. 2 2
      packages/core/src/llm/providers/endpoints/volcengineCodingPlan.ts
  40. 2 2
      packages/core/src/llm/providers/endpoints/wenxin.ts
  41. 2 2
      packages/core/src/llm/providers/endpoints/xai.ts
  42. 2 2
      packages/core/src/llm/providers/endpoints/xiaomimimo.ts
  43. 2 2
      packages/core/src/llm/providers/endpoints/zeroone.ts
  44. 2 2
      packages/core/src/llm/providers/endpoints/zhipu.ts
  45. 6 6
      packages/core/src/llm/providers/index.ts
  46. 4 4
      packages/core/src/llm/providers/lookup.ts
  47. 24 0
      packages/core/src/llm/providers/provider-to-pi-ai.ts
  48. 2 2
      packages/core/src/llm/providers/types.ts
  49. 3 3
      packages/core/src/llm/providers/verify.ts
  50. 3 3
      packages/core/src/llm/service-presets.ts

+ 22 - 22
packages/core/src/__tests__/providers-schema.test.ts

@@ -1,10 +1,10 @@
 import { describe, it, expect } from "vitest";
-import { getAllProviders, getProvider } from "../llm/providers/index.js";
+import { getAllEndpoints, getEndpoint } from "../llm/providers/index.js";
 
 describe("providers structural integrity", () => {
   it("每个 provider 必填字段都存在", () => {
     const gatewayProviders = new Set(["custom", "higress", "newapi"]);
-    for (const p of getAllProviders()) {
+    for (const p of getAllEndpoints()) {
       expect(p.id).toBeTruthy();
       expect(p.label).toBeTruthy();
       expect(p.api).toMatch(/^(openai-completions|openai-responses|anthropic-messages)$/);
@@ -18,7 +18,7 @@ describe("providers structural integrity", () => {
   });
 
   it("每个 model card 必填字段都存在且 contextWindowTokens >= maxOutput", () => {
-    for (const p of getAllProviders()) {
+    for (const p of getAllEndpoints()) {
       for (const m of p.models) {
         expect(m.id, `provider=${p.id}`).toBeTruthy();
         expect(m.maxOutput, `provider=${p.id} model=${m.id}`).toBeGreaterThan(0);
@@ -28,19 +28,19 @@ describe("providers structural integrity", () => {
   });
 
   it("每个 provider 的 id 唯一", () => {
-    const ids = getAllProviders().map((p) => p.id);
+    const ids = getAllEndpoints().map((p) => p.id);
     expect(new Set(ids).size).toBe(ids.length);
   });
 
   it("每个 provider 里 models 的 id 唯一", () => {
-    for (const p of getAllProviders()) {
+    for (const p of getAllEndpoints()) {
       const ids = p.models.map((m) => m.id);
       expect(new Set(ids).size, `provider=${p.id} 有重复 model id`).toBe(ids.length);
     }
   });
 
   it("A 组至少有 6 个核心 provider", () => {
-    const ids = getAllProviders().map((p) => p.id);
+    const ids = getAllEndpoints().map((p) => p.id);
     expect(ids).toContain("anthropic");
     expect(ids).toContain("openai");
     expect(ids).toContain("google");
@@ -50,7 +50,7 @@ describe("providers structural integrity", () => {
   });
 
   it("B1:中国原厂批次 1 全部收录(10 个)", () => {
-    const ids = getAllProviders().map((p) => p.id);
+    const ids = getAllEndpoints().map((p) => p.id);
     for (const id of [
       "moonshot", "zhipu", "siliconcloud", "ppio", "bailian",
       "volcengine", "hunyuan", "baichuan", "stepfun", "wenxin",
@@ -60,53 +60,53 @@ describe("providers structural integrity", () => {
   });
 
   it("B1:bailian 保留 anthropic-messages api(例外,不按 lobe 迁移)", () => {
-    expect(getProvider("bailian")?.api).toBe("anthropic-messages");
-    expect(getProvider("bailian")?.baseUrl).toContain("/anthropic");
+    expect(getEndpoint("bailian")?.api).toBe("anthropic-messages");
+    expect(getEndpoint("bailian")?.baseUrl).toContain("/anthropic");
   });
 
   it("B1:minimax 保留 anthropic-messages api(例外)", () => {
-    expect(getProvider("minimax")?.api).toBe("anthropic-messages");
-    expect(getProvider("minimax")?.baseUrl).toContain("/anthropic");
+    expect(getEndpoint("minimax")?.api).toBe("anthropic-messages");
+    expect(getEndpoint("minimax")?.baseUrl).toContain("/anthropic");
   });
 
   it("B2:中国原厂批次 2 全部收录(6 个)", () => {
-    const ids = getAllProviders().map((p) => p.id);
+    const ids = getAllEndpoints().map((p) => p.id);
     for (const id of ["spark", "sensenova", "tencentcloud", "xiaomimimo", "longcat", "internlm"]) {
       expect(ids).toContain(id);
     }
   });
 
   it("B3:中国原厂批次 3 全部收录(7 个)", () => {
-    const ids = getAllProviders().map((p) => p.id);
+    const ids = getAllEndpoints().map((p) => p.id);
     for (const id of ["modelscope", "giteeai", "qiniu", "higress", "infiniai", "zeroone", "ai360"]) {
       expect(ids).toContain(id);
     }
   });
 
   it("B3:higress baseUrl 为空(gateway 占位)", () => {
-    expect(getProvider("higress")?.baseUrl).toBe("");
+    expect(getEndpoint("higress")?.baseUrl).toBe("");
   });
 
   it("B4:海外/本地/自定义/聚合/GH 全部收录(7 个)", () => {
-    const ids = getAllProviders().map((p) => p.id);
+    const ids = getAllEndpoints().map((p) => p.id);
     for (const id of ["ollama", "openrouter", "custom", "mistral", "xai", "newapi", "githubCopilot"]) {
       expect(ids).toContain(id);
     }
   });
 
   it("B4:custom / newapi / higress baseUrl 为空", () => {
-    expect(getProvider("custom")?.baseUrl).toBe("");
-    expect(getProvider("newapi")?.baseUrl).toBe("");
-    expect(getProvider("higress")?.baseUrl).toBe("");
+    expect(getEndpoint("custom")?.baseUrl).toBe("");
+    expect(getEndpoint("newapi")?.baseUrl).toBe("");
+    expect(getEndpoint("higress")?.baseUrl).toBe("");
   });
 
   it("B4:总 provider 数 = 36(不含 CodingPlan)", () => {
-    const nonCoding = getAllProviders().filter((p) => !p.id.endsWith("CodingPlan"));
+    const nonCoding = getAllEndpoints().filter((p) => !p.id.endsWith("CodingPlan"));
     expect(nonCoding.length).toBe(36);
   });
 
   it("B6:CodingPlan 6 个 provider 全部收录", () => {
-    const ids = getAllProviders().map((p) => p.id);
+    const ids = getAllEndpoints().map((p) => p.id);
     for (const id of [
       "kimiCodingPlan", "minimaxCodingPlan", "bailianCodingPlan",
       "glmCodingPlan", "volcengineCodingPlan", "opencodeCodingPlan",
@@ -116,7 +116,7 @@ describe("providers structural integrity", () => {
   });
 
   it("B6:总 provider 数 = 42 (36 base + 6 CodingPlan)", () => {
-    expect(getAllProviders().length).toBe(42);
+    expect(getAllEndpoints().length).toBe(42);
   });
 
   it("B6:CodingPlan provider 都走 anthropic-messages", () => {
@@ -124,7 +124,7 @@ describe("providers structural integrity", () => {
       "kimiCodingPlan", "minimaxCodingPlan", "bailianCodingPlan",
       "glmCodingPlan", "volcengineCodingPlan", "opencodeCodingPlan",
     ]) {
-      expect(getProvider(id)?.api).toBe("anthropic-messages");
+      expect(getEndpoint(id)?.api).toBe("anthropic-messages");
     }
   });
 });

+ 7 - 3
packages/core/src/llm/provider.ts

@@ -15,8 +15,9 @@ import type {
   ToolCall as PiToolCall,
 } from "@mariozechner/pi-ai";
 import { resolveServicePreset } from "./service-presets.js";
-import { getProvider } from "./providers/index.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 ===
@@ -149,7 +150,7 @@ export function createLLMClient(config: LLMConfig): LLMClient {
   // --- Build pi-ai Model object ---
   const serviceName = config.service ?? "custom";
   const preset = resolveServicePreset(serviceName);
-  const inkosProvider = getProvider(serviceName);
+  const inkosProvider = getEndpoint(serviceName);
   const modelCard = lookupModel(serviceName, config.model);
 
   const piApi = resolvePiApi(serviceName, config.apiFormat, (inkosProvider?.api ?? preset?.api) as PiApi) as PiApi;
@@ -157,12 +158,15 @@ export function createLLMClient(config: LLMConfig): LLMClient {
   const extraHeaders = config.headers ?? parseEnvHeaders();
 
   const provider = config.provider === "anthropic" ? "anthropic" : "openai";
+  const piProvider = inkosProvider
+    ? resolvePiAiProvider(inkosProvider)
+    : provider;
 
   const piModel: PiModel<PiApi> = {
     id: modelCard?.deploymentName ?? config.model,
     name: modelCard?.displayName ?? config.model,
     api: piApi,
-    provider,
+    provider: piProvider,
     baseUrl,
     reasoning: modelCard?.abilities?.reasoning ?? (config.thinkingBudget ?? 0) > 0,
     input: modelCard?.abilities?.vision ? ["text", "image"] : ["text"],

+ 2 - 2
packages/core/src/llm/providers/endpoints/ai360.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const AI360: InkosProvider = {
+export const AI360: InkosEndpoint = {
   id: "ai360",
   label: "360 智脑",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/anthropic.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const ANTHROPIC: InkosProvider = {
+export const ANTHROPIC: InkosEndpoint = {
   id: "anthropic",
   label: "Anthropic",
   api: "anthropic-messages",

+ 2 - 2
packages/core/src/llm/providers/endpoints/baichuan.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const BAICHUAN: InkosProvider = {
+export const BAICHUAN: InkosEndpoint = {
   id: "baichuan",
   label: "百川智能",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/bailian.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const BAILIAN: InkosProvider = {
+export const BAILIAN: InkosEndpoint = {
   id: "bailian",
   label: "百炼 (通义千问)",
   api: "anthropic-messages",

+ 2 - 2
packages/core/src/llm/providers/endpoints/bailianCodingPlan.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const BAILIAN_CODING_PLAN: InkosProvider = {
+export const BAILIAN_CODING_PLAN: InkosEndpoint = {
   id: "bailianCodingPlan",
   label: "百炼 Coding Plan",
   api: "anthropic-messages",

+ 2 - 2
packages/core/src/llm/providers/endpoints/custom.ts

@@ -1,4 +1,4 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
 /**
  * custom 是"用户自填 baseUrl 的中转站"的锚点 provider。
@@ -7,7 +7,7 @@ import type { InkosProvider } from "../types.js";
  * listModelsForService 对 custom 用用户填的 baseUrl 走 live /models probe,
  * 失败再 fallback 到全局 bank 反查补元数据。
  */
-export const CUSTOM: InkosProvider = {
+export const CUSTOM: InkosEndpoint = {
   id: "custom",
   label: "自定义端点",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/deepseek.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const DEEPSEEK: InkosProvider = {
+export const DEEPSEEK: InkosEndpoint = {
   id: "deepseek",
   label: "DeepSeek",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/giteeai.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const GITEEAI: InkosProvider = {
+export const GITEEAI: InkosEndpoint = {
   id: "giteeai",
   label: "Gitee AI",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/githubCopilot.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const GITHUB_COPILOT: InkosProvider = {
+export const GITHUB_COPILOT: InkosEndpoint = {
   id: "githubCopilot",
   label: "GitHub Copilot",
   api: "openai-responses",

+ 2 - 2
packages/core/src/llm/providers/endpoints/glmCodingPlan.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const GLM_CODING_PLAN: InkosProvider = {
+export const GLM_CODING_PLAN: InkosEndpoint = {
   id: "glmCodingPlan",
   label: "GLM Coding Plan",
   api: "anthropic-messages",

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

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const GOOGLE: InkosProvider = {
+export const GOOGLE: InkosEndpoint = {
   id: "google",
   label: "Google Gemini",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/higress.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const HIGRESS: InkosProvider = {
+export const HIGRESS: InkosEndpoint = {
   id: "higress",
   label: "Higress (网关)",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/hunyuan.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const HUNYUAN: InkosProvider = {
+export const HUNYUAN: InkosEndpoint = {
   id: "hunyuan",
   label: "腾讯混元",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/infiniai.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const INFINIAI: InkosProvider = {
+export const INFINIAI: InkosEndpoint = {
   id: "infiniai",
   label: "无问芯穹 InfiniAI",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/internlm.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const INTERNLM: InkosProvider = {
+export const INTERNLM: InkosEndpoint = {
   id: "internlm",
   label: "书生浦语 (InternLM)",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/kimiCodingPlan.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const KIMI_CODING_PLAN: InkosProvider = {
+export const KIMI_CODING_PLAN: InkosEndpoint = {
   id: "kimiCodingPlan",
   label: "Kimi Coding Plan",
   api: "anthropic-messages",

+ 2 - 2
packages/core/src/llm/providers/endpoints/longcat.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const LONGCAT: InkosProvider = {
+export const LONGCAT: InkosEndpoint = {
   id: "longcat",
   label: "美团 LongCat",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/minimax.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const MINIMAX: InkosProvider = {
+export const MINIMAX: InkosEndpoint = {
   id: "minimax",
   label: "MiniMax",
   api: "anthropic-messages",

+ 2 - 2
packages/core/src/llm/providers/endpoints/minimaxCodingPlan.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const MINIMAX_CODING_PLAN: InkosProvider = {
+export const MINIMAX_CODING_PLAN: InkosEndpoint = {
   id: "minimaxCodingPlan",
   label: "MiniMax Coding Plan",
   api: "anthropic-messages",

+ 2 - 2
packages/core/src/llm/providers/endpoints/mistral.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const MISTRAL: InkosProvider = {
+export const MISTRAL: InkosEndpoint = {
   id: "mistral",
   label: "Mistral AI",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/modelscope.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const MODELSCOPE: InkosProvider = {
+export const MODELSCOPE: InkosEndpoint = {
   id: "modelscope",
   label: "魔搭社区 ModelScope",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/moonshot.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const MOONSHOT: InkosProvider = {
+export const MOONSHOT: InkosEndpoint = {
   id: "moonshot",
   label: "Moonshot (Kimi)",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/newapi.ts

@@ -1,11 +1,11 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
 /**
  * newapi 是 OneAPI / new-api 中转网关的锚点 provider。
  * 跟 custom 类似:baseUrl 由用户在 Studio 填(指向自己部署的 new-api 网关),
  * models 默认为空,实际可走 live /models probe(跟 custom 共用逻辑)。
  */
-export const NEWAPI: InkosProvider = {
+export const NEWAPI: InkosEndpoint = {
   id: "newapi",
   label: "New API (中转网关)",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/ollama.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const OLLAMA: InkosProvider = {
+export const OLLAMA: InkosEndpoint = {
   id: "ollama",
   label: "Ollama (本地)",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/openai.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const OPENAI: InkosProvider = {
+export const OPENAI: InkosEndpoint = {
   id: "openai",
   label: "OpenAI",
   api: "openai-responses",

+ 2 - 2
packages/core/src/llm/providers/endpoints/opencodeCodingPlan.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const OPENCODE_CODING_PLAN: InkosProvider = {
+export const OPENCODE_CODING_PLAN: InkosEndpoint = {
   id: "opencodeCodingPlan",
   label: "OpenCode Coding Plan",
   api: "anthropic-messages",

+ 2 - 2
packages/core/src/llm/providers/endpoints/openrouter.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const OPENROUTER: InkosProvider = {
+export const OPENROUTER: InkosEndpoint = {
   id: "openrouter",
   label: "OpenRouter",
   api: "openai-responses",

+ 2 - 2
packages/core/src/llm/providers/endpoints/ppio.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const PPIO: InkosProvider = {
+export const PPIO: InkosEndpoint = {
   id: "ppio",
   label: "PPIO",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/qiniu.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const QINIU: InkosProvider = {
+export const QINIU: InkosEndpoint = {
   id: "qiniu",
   label: "七牛云 AI",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/qwen.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const QWEN: InkosProvider = {
+export const QWEN: InkosEndpoint = {
   id: "qwen",
   label: "通义千问",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/sensenova.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const SENSENOVA: InkosProvider = {
+export const SENSENOVA: InkosEndpoint = {
   id: "sensenova",
   label: "商汤日日新",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/siliconcloud.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const SILICONCLOUD: InkosProvider = {
+export const SILICONCLOUD: InkosEndpoint = {
   id: "siliconcloud",
   label: "硅基流动",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/spark.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const SPARK: InkosProvider = {
+export const SPARK: InkosEndpoint = {
   id: "spark",
   label: "讯飞星火",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/stepfun.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const STEPFUN: InkosProvider = {
+export const STEPFUN: InkosEndpoint = {
   id: "stepfun",
   label: "阶跃星辰",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/tencentcloud.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const TENCENTCLOUD: InkosProvider = {
+export const TENCENTCLOUD: InkosEndpoint = {
   id: "tencentcloud",
   label: "腾讯云 (lkeap)",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/volcengine.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const VOLCENGINE: InkosProvider = {
+export const VOLCENGINE: InkosEndpoint = {
   id: "volcengine",
   label: "火山引擎 (豆包)",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/volcengineCodingPlan.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const VOLCENGINE_CODING_PLAN: InkosProvider = {
+export const VOLCENGINE_CODING_PLAN: InkosEndpoint = {
   id: "volcengineCodingPlan",
   label: "火山 Coding Plan",
   api: "anthropic-messages",

+ 2 - 2
packages/core/src/llm/providers/endpoints/wenxin.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const WENXIN: InkosProvider = {
+export const WENXIN: InkosEndpoint = {
   id: "wenxin",
   label: "文心一言 (千帆)",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/xai.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const XAI: InkosProvider = {
+export const XAI: InkosEndpoint = {
   id: "xai",
   label: "xAI (Grok)",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/xiaomimimo.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const XIAOMI_MIMO: InkosProvider = {
+export const XIAOMI_MIMO: InkosEndpoint = {
   id: "xiaomimimo",
   label: "小米 MiMo",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/zeroone.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const ZEROONE: InkosProvider = {
+export const ZEROONE: InkosEndpoint = {
   id: "zeroone",
   label: "零一万物 (01.AI)",
   api: "openai-completions",

+ 2 - 2
packages/core/src/llm/providers/endpoints/zhipu.ts

@@ -1,6 +1,6 @@
-import type { InkosProvider } from "../types.js";
+import type { InkosEndpoint } from "../types.js";
 
-export const ZHIPU: InkosProvider = {
+export const ZHIPU: InkosEndpoint = {
   id: "zhipu",
   label: "智谱 GLM",
   api: "openai-completions",

+ 6 - 6
packages/core/src/llm/providers/index.ts

@@ -1,4 +1,4 @@
-import type { InkosProvider } from "./types.js";
+import type { InkosEndpoint } from "./types.js";
 import { ANTHROPIC } from "./endpoints/anthropic.js";
 import { OPENAI } from "./endpoints/openai.js";
 import { GOOGLE } from "./endpoints/google.js";
@@ -47,13 +47,13 @@ import { GLM_CODING_PLAN } from "./endpoints/glmCodingPlan.js";
 import { VOLCENGINE_CODING_PLAN } from "./endpoints/volcengineCodingPlan.js";
 import { OPENCODE_CODING_PLAN } from "./endpoints/opencodeCodingPlan.js";
 
-export type { InkosProvider, InkosModel, ApiProtocol } from "./types.js";
+export type { InkosEndpoint, InkosModel, ApiProtocol } from "./types.js";
 
 /**
  * 所有已注册 provider 的扁平列表。顺序定义了 lookup Layer 2 的遍历顺序,
  * 但 Layer 2 还会按 PROVIDER_PRIORITY 显式排序,所以此处顺序不影响结果。
  */
-const ALL_PROVIDERS: readonly InkosProvider[] = [
+const ALL_PROVIDERS: readonly InkosEndpoint[] = [
   ANTHROPIC, OPENAI, GOOGLE, DEEPSEEK, QWEN, MINIMAX,
   MOONSHOT, ZHIPU, SILICONCLOUD, PPIO, BAILIAN, VOLCENGINE, HUNYUAN, BAICHUAN, STEPFUN, WENXIN,
   SPARK, SENSENOVA, TENCENTCLOUD, XIAOMI_MIMO, LONGCAT, INTERNLM,
@@ -63,14 +63,14 @@ const ALL_PROVIDERS: readonly InkosProvider[] = [
   KIMI_CODING_PLAN, MINIMAX_CODING_PLAN, BAILIAN_CODING_PLAN, GLM_CODING_PLAN, VOLCENGINE_CODING_PLAN, OPENCODE_CODING_PLAN,
 ];
 
-const PROVIDERS_BY_ID: Map<string, InkosProvider> = new Map(
+const PROVIDERS_BY_ID: Map<string, InkosEndpoint> = new Map(
   ALL_PROVIDERS.map((p) => [p.id, p]),
 );
 
-export function getAllProviders(): readonly InkosProvider[] {
+export function getAllEndpoints(): readonly InkosEndpoint[] {
   return ALL_PROVIDERS;
 }
 
-export function getProvider(id: string): InkosProvider | undefined {
+export function getEndpoint(id: string): InkosEndpoint | undefined {
   return PROVIDERS_BY_ID.get(id);
 }

+ 4 - 4
packages/core/src/llm/providers/lookup.ts

@@ -1,5 +1,5 @@
 import type { InkosModel } from "./types.js";
-import { getAllProviders, getProvider } from "./index.js";
+import { getAllEndpoints, getEndpoint } from "./index.js";
 
 /**
  * provider id 优先级。Layer 2 全局扫时同 id 多条匹配按这个顺序取第一条,
@@ -27,14 +27,14 @@ export function lookupModel(
 ): InkosModel | undefined {
   const lowerId = modelId.toLowerCase();
 
-  const provider = getProvider(serviceId);
+  const provider = getEndpoint(serviceId);
   if (provider) {
     const hit = provider.models.find((m) => m.id.toLowerCase() === lowerId);
     if (hit) return hit;
   }
 
   const matches: Array<{ model: InkosModel; providerId: string }> = [];
-  for (const p of getAllProviders()) {
+  for (const p of getAllEndpoints()) {
     const hit = p.models.find((m) => m.id.toLowerCase() === lowerId);
     if (hit) matches.push({ model: hit, providerId: p.id });
   }
@@ -50,7 +50,7 @@ export function lookupModel(
 
 /** 某 service 下可用(enabled !== false)的模型列表 */
 export function listEnabledModels(serviceId: string): InkosModel[] {
-  const provider = getProvider(serviceId);
+  const provider = getEndpoint(serviceId);
   if (!provider) return [];
   return provider.models.filter((m) => m.enabled !== false);
 }

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

@@ -0,0 +1,24 @@
+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";
+}

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

@@ -1,6 +1,6 @@
 /**
  * inkos 自维护的 provider 定义。每个 provider 一个 .ts 文件,
- * 里面一个 InkosProvider 对象(provider 元数据 + models 数组)。
+ * 里面一个 InkosEndpoint 对象(provider 元数据 + models 数组)。
  *
  * 数据冷启动自 lobe-chat/packages/model-bank,之后由 inkos 自管。
  * 新模型发布 / 参数调整时手动加 card,不做持续 sync。
@@ -36,7 +36,7 @@ export interface InkosModel {
   readonly releasedAt?: string;
 }
 
-export interface InkosProvider {
+export interface InkosEndpoint {
   readonly id: string;
   readonly label: string;
 

+ 3 - 3
packages/core/src/llm/providers/verify.ts

@@ -1,4 +1,4 @@
-import { getProvider } from "./index.js";
+import { getEndpoint } from "./index.js";
 import { resolveServiceModelsBaseUrl } from "../service-presets.js";
 
 export interface VerifyResult {
@@ -23,7 +23,7 @@ async function probe(
   apiKey: string,
   baseUrl?: string,
 ): Promise<VerifyResult["probe"]> {
-  const provider = getProvider(service);
+  const provider = getEndpoint(service);
   const probeBaseUrl = baseUrl || provider?.modelsBaseUrl || provider?.baseUrl || resolveServiceModelsBaseUrl(service);
   if (!probeBaseUrl) {
     return { ok: false, models: 0, error: "无 baseUrl 可探测(custom / newapi / higress 需要用户填)" };
@@ -61,7 +61,7 @@ export async function verifyService(
 ): Promise<VerifyResult> {
   const probeResult = await probe(service, apiKey, opts?.baseUrl);
 
-  const provider = getProvider(service);
+  const provider = getEndpoint(service);
   const checkModel = opts?.checkModel ?? provider?.checkModel;
   if (!checkModel) {
     return { probe: probeResult, chat: null };

+ 3 - 3
packages/core/src/llm/service-presets.ts

@@ -1,4 +1,4 @@
-import { getProvider } from "./providers/index.js";
+import { getEndpoint } from "./providers/index.js";
 
 export interface ServicePreset {
   readonly providerFamily: "openai" | "anthropic";
@@ -50,7 +50,7 @@ export const SERVICE_PRESETS: Record<string, ServicePreset> = {
 };
 
 export function resolveServicePreset(service: string): ServicePreset | undefined {
-  const provider = getProvider(service);
+  const provider = getEndpoint(service);
   const legacy = SERVICE_PRESETS[service];
   if (!provider && !legacy) return undefined;
 
@@ -170,7 +170,7 @@ export async function listModelsForService(
   apiKey?: string,
   liveBaseUrl?: string,
 ): Promise<ReadonlyArray<ModelInfo>> {
-  const provider = getProvider(service);
+  const provider = getEndpoint(service);
   const preset = SERVICE_PRESETS[service];
   if (!provider && !preset) return [];