diff --git a/CHANGELOG.md b/CHANGELOG.md index fa6bdd3fb..b3b3879ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Development since v3.16.1 focuses on broadening data portability and usage obser - **Unsupported Image Fallback Rectifier**: Added a proxy rectifier that replaces Anthropic image blocks with an `[Unsupported Image]` marker when the routed model is text-only (declared, or detected via a built-in model-name heuristic) or when the upstream rejects image input, so conversations are not interrupted. A new Settings toggle controls the fallback, with a separate toggle for the heuristic detection. - **ZenMux Token Plan Provider**: Added ZenMux as a Token Plan coding-plan provider that accepts a manually entered API key and base URL in the usage-script modal and renders its quota with USD-denominated used / limit values (#2709). - **CherryIN Preset**: Added the CherryIN aggregator gateway as a quick-config preset across all seven supported apps — Anthropic-format endpoint for Claude Code / Claude Desktop / OpenClaw / Hermes, `@ai-sdk/anthropic` for OpenCode, the OpenAI-compatible endpoint for Codex, and the Gemini-compatible endpoint for Gemini CLI — with the official brand icon, placed next to AiHubMix (#3643). +- **CCSub Preset**: Added CCSub, a multi-model aggregator partner, as a quick-config preset across six apps — Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, and Hermes — with the official brand icon and the partner referral link prefilled as the API-key signup URL (`gpt-5.5` for the OpenAI-compatible Codex and OpenCode endpoints). - **Codex CLI Models Endpoint**: The local proxy now answers `GET /v1/models`, which Codex CLI probes at startup, returning the cc-switch-managed Codex model catalog. A stale-catalog guard parses the live `config.toml` and only serves the catalog when `model_catalog_json` still references the cc-switch-owned file, so a leftover catalog from a previous provider is not advertised (#3818). - **Codex Chat File and Audio Attachments**: The Codex Responses-to-Chat converter now maps `input_file` parts (carrying `file_id` or inline `file_data`) and `input_audio` parts into their Chat Completions equivalents, and emits top-level `input_*` items that were previously dropped, so file and audio attachments reach Chat-only Codex upstreams. diff --git a/src/config/claudeDesktopProviderPresets.ts b/src/config/claudeDesktopProviderPresets.ts index 8ac82b580..399c5c30c 100644 --- a/src/config/claudeDesktopProviderPresets.ts +++ b/src/config/claudeDesktopProviderPresets.ts @@ -167,19 +167,6 @@ export const claudeDesktopProviderPresets: ClaudeDesktopProviderPreset[] = [ partnerPromotionKey: "shengsuanyun", icon: "shengsuanyun", }, - { - name: "CCSub", - websiteUrl: "https://www.ccsub.net", - apiKeyUrl: "https://www.ccsub.net/register?ref=Y6Z8DXEA", - category: "aggregator", - baseUrl: "https://www.ccsub.net", - mode: "direct", - apiFormat: "anthropic", - modelRoutes: passthroughRoutes(true), - isPartner: true, - partnerPromotionKey: "ccsub", - icon: "ccsub", - }, { name: "PatewayAI", websiteUrl: "https://pateway.ai", @@ -253,6 +240,19 @@ export const claudeDesktopProviderPresets: ClaudeDesktopProviderPreset[] = [ icon: "doubao", iconColor: "#3370FF", }, + { + name: "CCSub", + websiteUrl: "https://www.ccsub.net", + apiKeyUrl: "https://www.ccsub.net/register?ref=Y6Z8DXEA", + category: "aggregator", + baseUrl: "https://www.ccsub.net", + mode: "direct", + apiFormat: "anthropic", + modelRoutes: passthroughRoutes(true), + isPartner: true, + partnerPromotionKey: "ccsub", + icon: "ccsub", + }, { name: "Gemini Native", websiteUrl: "https://ai.google.dev/gemini-api", diff --git a/src/config/claudeProviderPresets.ts b/src/config/claudeProviderPresets.ts index 88bd2e6dc..18f09f5c7 100644 --- a/src/config/claudeProviderPresets.ts +++ b/src/config/claudeProviderPresets.ts @@ -109,21 +109,6 @@ export const providerPresets: ProviderPreset[] = [ partnerPromotionKey: "shengsuanyun", icon: "shengsuanyun", }, - { - name: "CCSub", - websiteUrl: "https://www.ccsub.net", - apiKeyUrl: "https://www.ccsub.net/register?ref=Y6Z8DXEA", - settingsConfig: { - env: { - ANTHROPIC_BASE_URL: "https://www.ccsub.net", - ANTHROPIC_AUTH_TOKEN: "", - }, - }, - category: "aggregator", - isPartner: true, - partnerPromotionKey: "ccsub", - icon: "ccsub", - }, { name: "PatewayAI", websiteUrl: "https://pateway.ai", @@ -208,6 +193,21 @@ export const providerPresets: ProviderPreset[] = [ icon: "doubao", iconColor: "#3370FF", }, + { + name: "CCSub", + websiteUrl: "https://www.ccsub.net", + apiKeyUrl: "https://www.ccsub.net/register?ref=Y6Z8DXEA", + settingsConfig: { + env: { + ANTHROPIC_BASE_URL: "https://www.ccsub.net", + ANTHROPIC_AUTH_TOKEN: "", + }, + }, + category: "aggregator", + isPartner: true, + partnerPromotionKey: "ccsub", + icon: "ccsub", + }, { name: "Gemini Native", websiteUrl: "https://ai.google.dev/gemini-api", diff --git a/src/config/codexProviderPresets.ts b/src/config/codexProviderPresets.ts index d2e6aeb6d..160a54255 100644 --- a/src/config/codexProviderPresets.ts +++ b/src/config/codexProviderPresets.ts @@ -116,22 +116,6 @@ export const codexProviderPresets: CodexProviderPreset[] = [ partnerPromotionKey: "shengsuanyun", icon: "shengsuanyun", }, - { - name: "CCSub", - websiteUrl: "https://www.ccsub.net", - apiKeyUrl: "https://www.ccsub.net/register?ref=Y6Z8DXEA", - category: "aggregator", - auth: generateThirdPartyAuth(""), - config: generateThirdPartyConfig( - "ccsub", - "https://www.ccsub.net/v1", - "gpt-5.5", - ), - endpointCandidates: ["https://www.ccsub.net/v1"], - isPartner: true, - partnerPromotionKey: "ccsub", - icon: "ccsub", - }, { name: "PatewayAI", websiteUrl: "https://pateway.ai", @@ -231,6 +215,22 @@ export const codexProviderPresets: CodexProviderPreset[] = [ icon: "doubao", iconColor: "#3370FF", }, + { + name: "CCSub", + websiteUrl: "https://www.ccsub.net", + apiKeyUrl: "https://www.ccsub.net/register?ref=Y6Z8DXEA", + category: "aggregator", + auth: generateThirdPartyAuth(""), + config: generateThirdPartyConfig( + "ccsub", + "https://www.ccsub.net/v1", + "gpt-5.5", + ), + endpointCandidates: ["https://www.ccsub.net/v1"], + isPartner: true, + partnerPromotionKey: "ccsub", + icon: "ccsub", + }, { name: "Azure OpenAI", websiteUrl: diff --git a/src/config/hermesProviderPresets.ts b/src/config/hermesProviderPresets.ts index fda9561db..244e7f521 100644 --- a/src/config/hermesProviderPresets.ts +++ b/src/config/hermesProviderPresets.ts @@ -238,6 +238,56 @@ export const hermesProviderPresets: HermesProviderPreset[] = [ }, }, }, + { + name: "CCSub", + websiteUrl: "https://www.ccsub.net", + apiKeyUrl: "https://www.ccsub.net/register?ref=Y6Z8DXEA", + settingsConfig: { + name: "ccsub", + base_url: "https://www.ccsub.net/v1", + api_key: "", + api_mode: "chat_completions", + models: [ + { + id: "claude-opus-4-8", + name: "Claude Opus 4.8", + context_length: 1000000, + }, + { + id: "claude-sonnet-4-6", + name: "Claude Sonnet 4.6", + context_length: 1000000, + }, + { + id: "gpt-5.5", + name: "GPT-5.5", + context_length: 400000, + }, + { + id: "o3", + name: "o3", + context_length: 200000, + }, + { + id: "gemini-3.1-pro", + name: "Gemini 3.1 Pro", + context_length: 1000000, + }, + { + id: "deepseek-v4-flash", + name: "DeepSeek V4 Flash", + context_length: 1000000, + }, + ], + }, + category: "aggregator", + isPartner: true, + partnerPromotionKey: "ccsub", + icon: "ccsub", + suggestedDefaults: { + model: { default: "claude-opus-4-8", provider: "ccsub" }, + }, + }, { name: "OpenRouter", nameKey: "providerForm.presets.openrouter", @@ -351,56 +401,6 @@ export const hermesProviderPresets: HermesProviderPreset[] = [ }, }, }, - { - name: "CCSub", - websiteUrl: "https://www.ccsub.net", - apiKeyUrl: "https://www.ccsub.net/register?ref=Y6Z8DXEA", - settingsConfig: { - name: "ccsub", - base_url: "https://www.ccsub.net/v1", - api_key: "", - api_mode: "chat_completions", - models: [ - { - id: "claude-opus-4-8", - name: "Claude Opus 4.8", - context_length: 1000000, - }, - { - id: "claude-sonnet-4-6", - name: "Claude Sonnet 4.6", - context_length: 1000000, - }, - { - id: "gpt-5.5", - name: "GPT-5.5", - context_length: 400000, - }, - { - id: "o3", - name: "o3", - context_length: 200000, - }, - { - id: "gemini-3.1-pro", - name: "Gemini 3.1 Pro", - context_length: 1000000, - }, - { - id: "deepseek-v4-flash", - name: "DeepSeek V4 Flash", - context_length: 1000000, - }, - ], - }, - category: "aggregator", - isPartner: true, - partnerPromotionKey: "ccsub", - icon: "ccsub", - suggestedDefaults: { - model: { default: "claude-opus-4-8", provider: "ccsub" }, - }, - }, { name: "Nous Research", websiteUrl: "https://nousresearch.com", diff --git a/src/config/openclawProviderPresets.ts b/src/config/openclawProviderPresets.ts index 223327177..0b168da37 100644 --- a/src/config/openclawProviderPresets.ts +++ b/src/config/openclawProviderPresets.ts @@ -256,6 +256,75 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ }, }, }, + { + name: "CCSub", + websiteUrl: "https://www.ccsub.net", + apiKeyUrl: "https://www.ccsub.net/register?ref=Y6Z8DXEA", + settingsConfig: { + baseUrl: "https://www.ccsub.net/v1", + apiKey: "", + api: "openai-completions", + models: [ + { + id: "claude-opus-4-8", + name: "Claude Opus 4.8", + contextWindow: 1000000, + cost: { input: 5, output: 25 }, + }, + { + id: "claude-sonnet-4-6", + name: "Claude Sonnet 4.6", + contextWindow: 1000000, + cost: { input: 3, output: 15 }, + }, + { + id: "gpt-5.5", + name: "GPT-5.5", + contextWindow: 400000, + cost: { input: 5, output: 15 }, + }, + { + id: "o3", + name: "o3", + contextWindow: 200000, + cost: { input: 10, output: 40 }, + }, + { + id: "gemini-3.1-pro", + name: "Gemini 3.1 Pro", + contextWindow: 1000000, + cost: { input: 1.25, output: 10 }, + }, + { + id: "deepseek-v4-flash", + name: "DeepSeek V4 Flash", + contextWindow: 1000000, + cost: { input: 0.14, output: 0.28 }, + }, + ], + }, + category: "aggregator", + isPartner: true, + partnerPromotionKey: "ccsub", + icon: "ccsub", + templateValues: { + apiKey: { + label: "API Key", + placeholder: "", + editorValue: "", + }, + }, + suggestedDefaults: { + model: { + primary: "ccsub/claude-opus-4-8", + fallbacks: ["ccsub/claude-sonnet-4-6"], + }, + modelCatalog: { + "ccsub/claude-opus-4-8": { alias: "Opus" }, + "ccsub/claude-sonnet-4-6": { alias: "Sonnet" }, + }, + }, + }, // ========== Chinese Officials ========== { name: "DeepSeek", @@ -900,75 +969,6 @@ export const openclawProviderPresets: OpenClawProviderPreset[] = [ }, }, }, - { - name: "CCSub", - websiteUrl: "https://www.ccsub.net", - apiKeyUrl: "https://www.ccsub.net/register?ref=Y6Z8DXEA", - settingsConfig: { - baseUrl: "https://www.ccsub.net/v1", - apiKey: "", - api: "openai-completions", - models: [ - { - id: "claude-opus-4-8", - name: "Claude Opus 4.8", - contextWindow: 1000000, - cost: { input: 5, output: 25 }, - }, - { - id: "claude-sonnet-4-6", - name: "Claude Sonnet 4.6", - contextWindow: 1000000, - cost: { input: 3, output: 15 }, - }, - { - id: "gpt-5.5", - name: "GPT-5.5", - contextWindow: 400000, - cost: { input: 5, output: 15 }, - }, - { - id: "o3", - name: "o3", - contextWindow: 200000, - cost: { input: 10, output: 40 }, - }, - { - id: "gemini-3.1-pro", - name: "Gemini 3.1 Pro", - contextWindow: 1000000, - cost: { input: 1.25, output: 10 }, - }, - { - id: "deepseek-v4-flash", - name: "DeepSeek V4 Flash", - contextWindow: 1000000, - cost: { input: 0.14, output: 0.28 }, - }, - ], - }, - category: "aggregator", - isPartner: true, - partnerPromotionKey: "ccsub", - icon: "ccsub", - templateValues: { - apiKey: { - label: "API Key", - placeholder: "", - editorValue: "", - }, - }, - suggestedDefaults: { - model: { - primary: "ccsub/claude-opus-4-8", - fallbacks: ["ccsub/claude-sonnet-4-6"], - }, - modelCatalog: { - "ccsub/claude-opus-4-8": { alias: "Opus" }, - "ccsub/claude-sonnet-4-6": { alias: "Sonnet" }, - }, - }, - }, { name: "CherryIN", websiteUrl: "https://open.cherryin.ai", diff --git a/src/config/opencodeProviderPresets.ts b/src/config/opencodeProviderPresets.ts index 103aacdf0..4d6cda1e2 100644 --- a/src/config/opencodeProviderPresets.ts +++ b/src/config/opencodeProviderPresets.ts @@ -308,39 +308,6 @@ export const opencodeProviderPresets: OpenCodeProviderPreset[] = [ }, }, }, - { - name: "CCSub", - websiteUrl: "https://www.ccsub.net", - apiKeyUrl: "https://www.ccsub.net/register?ref=Y6Z8DXEA", - settingsConfig: { - npm: "@ai-sdk/openai-compatible", - name: "CCSub", - options: { - baseURL: "https://www.ccsub.net/v1", - apiKey: "", - setCacheKey: true, - }, - models: { - "claude-opus-4-8": { name: "Claude Opus 4.8" }, - "claude-sonnet-4-6": { name: "Claude Sonnet 4.6" }, - "gpt-5.5": { name: "GPT-5.5" }, - o3: { name: "o3" }, - "gemini-3.1-pro": { name: "Gemini 3.1 Pro" }, - "deepseek-v4-flash": { name: "DeepSeek V4 Flash" }, - }, - }, - category: "aggregator", - isPartner: true, - partnerPromotionKey: "ccsub", - icon: "ccsub", - templateValues: { - apiKey: { - label: "API Key", - placeholder: "", - editorValue: "", - }, - }, - }, { name: "火山Agentplan", websiteUrl: @@ -440,6 +407,39 @@ export const opencodeProviderPresets: OpenCodeProviderPreset[] = [ }, }, }, + { + name: "CCSub", + websiteUrl: "https://www.ccsub.net", + apiKeyUrl: "https://www.ccsub.net/register?ref=Y6Z8DXEA", + settingsConfig: { + npm: "@ai-sdk/openai-compatible", + name: "CCSub", + options: { + baseURL: "https://www.ccsub.net/v1", + apiKey: "", + setCacheKey: true, + }, + models: { + "claude-opus-4-8": { name: "Claude Opus 4.8" }, + "claude-sonnet-4-6": { name: "Claude Sonnet 4.6" }, + "gpt-5.5": { name: "GPT-5.5" }, + o3: { name: "o3" }, + "gemini-3.1-pro": { name: "Gemini 3.1 Pro" }, + "deepseek-v4-flash": { name: "DeepSeek V4 Flash" }, + }, + }, + category: "aggregator", + isPartner: true, + partnerPromotionKey: "ccsub", + icon: "ccsub", + templateValues: { + apiKey: { + label: "API Key", + placeholder: "", + editorValue: "", + }, + }, + }, { name: "DeepSeek", websiteUrl: "https://platform.deepseek.com", diff --git a/tests/config/providerPresetOrder.test.ts b/tests/config/providerPresetOrder.test.ts deleted file mode 100644 index 4f42c8f09..000000000 --- a/tests/config/providerPresetOrder.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { providerPresets } from "@/config/claudeProviderPresets"; -import { claudeDesktopProviderPresets } from "@/config/claudeDesktopProviderPresets"; -import { codexProviderPresets } from "@/config/codexProviderPresets"; -import { opencodeProviderPresets } from "@/config/opencodeProviderPresets"; -import { openclawProviderPresets } from "@/config/openclawProviderPresets"; -import { hermesProviderPresets } from "@/config/hermesProviderPresets"; - -const namesOf = (presets: Array<{ name: string }>) => - presets.map((preset) => preset.name); - -const expectInOrder = (names: string[], expected: string[]) => { - const indexes = expected.map((name) => names.indexOf(name)); - - expect(indexes).not.toContain(-1); - expect(indexes).toEqual(expected.map((_, index) => indexes[0] + index)); -}; - -describe("provider preset order", () => { - it("Claude 预设按合作伙伴优先顺序排列", () => { - expectInOrder(namesOf(providerPresets), [ - "Shengsuanyun", - "PatewayAI", - "火山Agentplan", - "BytePlus", - "DouBaoSeed", - ]); - }); - - it("Claude Desktop 预设按合作伙伴优先顺序排列", () => { - expectInOrder(namesOf(claudeDesktopProviderPresets), [ - "Shengsuanyun", - "PatewayAI", - "火山Agentplan", - "BytePlus", - "DouBaoSeed", - ]); - }); - - it("Claude Desktop 预设包含官方登录入口", () => { - expect(claudeDesktopProviderPresets[0]).toMatchObject({ - name: "Claude Desktop Official", - category: "official", - baseUrl: "", - mode: "direct", - }); - }); - - it("Codex 预设按合作伙伴优先顺序排列", () => { - expectInOrder(namesOf(codexProviderPresets), [ - "Shengsuanyun", - "PatewayAI", - "火山Agentplan", - "BytePlus", - "DouBaoSeed", - ]); - }); - - it("OpenCode 预设把火山、BytePlus、DouBaoSeed 放在胜算云后面", () => { - expectInOrder(namesOf(opencodeProviderPresets), [ - "Shengsuanyun", - "火山Agentplan", - "BytePlus", - "DouBaoSeed", - ]); - }); - - it("OpenClaw 预设把火山、BytePlus、DouBaoSeed 放在胜算云后面", () => { - expectInOrder(namesOf(openclawProviderPresets), [ - "Shengsuanyun", - "火山Agentplan", - "BytePlus", - "DouBaoSeed", - ]); - }); - - it("Hermes 预设把火山、BytePlus、DouBaoSeed 放在胜算云后面", () => { - expectInOrder(namesOf(hermesProviderPresets), [ - "Shengsuanyun", - "火山Agentplan", - "BytePlus", - "DouBaoSeed", - ]); - }); -});