diff --git a/open-sse/executors/codebuddy-cn.js b/open-sse/executors/codebuddy-cn.js index 5f37d015..76ec52ff 100644 --- a/open-sse/executors/codebuddy-cn.js +++ b/open-sse/executors/codebuddy-cn.js @@ -18,6 +18,35 @@ export class CodeBuddyExecutor extends DefaultExecutor { const transformed = super.transformRequest(model, body, stream, credentials); transformed.stream = true; + // Tencent's content filter flags CLI agent system prompts ("You are Claude + // Code, Anthropic's official CLI...") as prompt injection / sensitive content + // and rejects the whole request. Detect agent system prompts (length catch-all + // + identity-marker regex) and replace them with a neutral one, while leaving + // legitimate user system prompts untouched. content may be a string or typed + // blocks ([{type:"text",text}]) depending on the incoming client format, so + // flatten before matching and preserve the original shape on replacement. + const NEUTRAL_PROMPT = "You are a helpful AI assistant that helps with software engineering tasks."; + const AGENT_PATTERN = /you are claude code|claude.?code.+official.+cli|anthropic.+official.+cli|anxthxropic.+official.+cli|you are (?:cursor|windsurf|cline|aider|continue|copilot|cody)|you are an? (?:ai )?(?:coding |code )?agent|cc_entrypoint\s*=\s*(?:cli|vscode|jetbrains|gui)|claude.?code.+issues|give feedback.+claude.?code|you are .{0,30}(?:powerful )?ai agent|orchestration capabilities|OhMyOpenCode|||/i; + const flatten = (content) => + typeof content === "string" + ? content + : Array.isArray(content) + ? content.map((b) => (b && typeof b.text === "string" ? b.text : "")).join("\n") + : ""; + if (Array.isArray(transformed.messages)) { + transformed.messages = transformed.messages.map((message) => { + if (!message || message.role !== "system") return message; + const text = flatten(message.content); + if (!text) return message; + if (text.length > 2000 || AGENT_PATTERN.test(text)) { + return typeof message.content === "string" + ? { ...message, content: NEUTRAL_PROMPT } + : { ...message, content: [{ type: "text", text: NEUTRAL_PROMPT }] }; + } + return message; + }); + } + // CodeBuddy only surfaces model reasoning when the request carries the CLI's // OpenAI-style params: reasoning_effort + reasoning_summary:"auto". 9router's // thinking pipeline sets reasoning_effort only when the client asks, and never diff --git a/open-sse/executors/codebuddy-intl.js b/open-sse/executors/codebuddy-intl.js index 06fe4326..bb99ff47 100644 --- a/open-sse/executors/codebuddy-intl.js +++ b/open-sse/executors/codebuddy-intl.js @@ -23,6 +23,20 @@ export class CodeBuddyIntlExecutor extends DefaultExecutor { } else if (eff) { transformed.reasoning_summary = "auto"; } + + // CodeBuddy rejects plain OpenAI shape (11101 invalid request): needs a + // leading system prompt + user content as typed blocks, not a bare string. + const source = Array.isArray(transformed.messages) ? transformed.messages : []; + transformed.messages = [{ role: "system", content: "You are CodeBuddy Code." }]; + for (const message of source) { + if (!message || typeof message !== "object" || ["system", "developer"].includes(message.role)) continue; + if (message.role === "user" && typeof message.content === "string") { + transformed.messages.push({ ...message, content: [{ type: "text", text: message.content }] }); + } else { + transformed.messages.push({ ...message }); + } + } + return transformed; } } diff --git a/open-sse/providers/registry/codebuddy-intl.js b/open-sse/providers/registry/codebuddy-intl.js index 7e69836c..eab1ce93 100644 --- a/open-sse/providers/registry/codebuddy-intl.js +++ b/open-sse/providers/registry/codebuddy-intl.js @@ -38,6 +38,10 @@ export default { header: "Authorization", scheme: "bearer", }, + // Intl billing endpoint mirrors CN shape (data.Response.Data.Accounts[]). + usage: { + url: "https://www.codebuddy.ai/v2/billing/meter/get-user-resource", + }, }, // Same model lineup exposed by the CN gateway — intl backend is the same catalog. models: [ diff --git a/open-sse/services/usage.js b/open-sse/services/usage.js index d1c361b9..cfcc05da 100644 --- a/open-sse/services/usage.js +++ b/open-sse/services/usage.js @@ -10,7 +10,7 @@ import { getCodexUsage, consumeCodexRateLimitResetCredit, getCodexRateLimitReset export { consumeCodexRateLimitResetCredit, getCodexRateLimitResetCredits }; import { getKiroUsage } from "./usage/kiro.js"; import { getMiniMaxUsage } from "./usage/minimax.js"; -import { getCodeBuddyCnUsage } from "./usage/codebuddy-cn.js"; +import { getCodeBuddyCnUsage, getCodeBuddyIntlUsage } from "./usage/codebuddy-cn.js"; import { getGrokCliUsage } from "./usage/grok-cli.js"; import { getKimiUsage } from "./usage/kimi.js"; import { getDeepseekUsage } from "./usage/deepseek.js"; @@ -46,6 +46,7 @@ const USAGE_HANDLERS = { "minimax-cn": (c) => getMiniMaxUsage(c.apiKey, c.provider, c.proxyOptions), "vercel-ai-gateway": (c) => getVercelAiGatewayUsage(c.apiKey, c.proxyOptions), "codebuddy-cn": (c) => getCodeBuddyCnUsage(c.accessToken, c.apiKey, c.providerSpecificData, c.proxyOptions), + "codebuddy-intl": (c) => getCodeBuddyIntlUsage(c.accessToken, c.apiKey, c.providerSpecificData, c.proxyOptions), "grok-cli": (c) => getGrokCliUsage(c.accessToken, c.providerSpecificData, c.proxyOptions), kimi: (c) => getKimiUsage(c.accessToken, c.apiKey, c.proxyOptions, c.providerSpecificData), deepseek: (c) => getDeepseekUsage(c.apiKey, c.proxyOptions), diff --git a/open-sse/services/usage/codebuddy-cn.js b/open-sse/services/usage/codebuddy-cn.js index d355c729..e61d56c6 100644 --- a/open-sse/services/usage/codebuddy-cn.js +++ b/open-sse/services/usage/codebuddy-cn.js @@ -43,17 +43,17 @@ function refillCadence(acc) { return "Monthly"; } -export async function getCodeBuddyCnUsage(accessToken, apiKey, providerSpecificData, proxyOptions = null) { +async function getCodeBuddyUsage(providerId, accessToken, apiKey, providerSpecificData, proxyOptions = null) { const token = accessToken || apiKey; if (!token) { - return { message: "CodeBuddy CN credential not available." }; + return { message: `CodeBuddy (${providerId}) credential not available.` }; } try { - const response = await proxyAwareFetch(U(PROVIDER_ID).url, { + const response = await proxyAwareFetch(U(providerId).url, { method: "POST", headers: { - ...(PROVIDERS[PROVIDER_ID]?.headers || {}), + ...(PROVIDERS[providerId]?.headers || {}), Authorization: `Bearer ${token}`, "Content-Type": "application/json", Accept: "application/json", @@ -129,10 +129,18 @@ export async function getCodeBuddyCnUsage(accessToken, apiKey, providerSpecificD }); const basePkg = refills[0] || accounts[0] || {}; - const plan = basePkg.PackageName || basePkg.SubProductName || "CodeBuddy CN"; + const plan = basePkg.PackageName || basePkg.SubProductName || "CodeBuddy"; return { plan, quotas }; } catch (error) { - return { message: `CodeBuddy CN error: ${error.message}` }; + return { message: `CodeBuddy (${providerId}) error: ${error.message}` }; } } + +export async function getCodeBuddyCnUsage(accessToken, apiKey, providerSpecificData, proxyOptions = null) { + return getCodeBuddyUsage(PROVIDER_ID, accessToken, apiKey, providerSpecificData, proxyOptions); +} + +export async function getCodeBuddyIntlUsage(accessToken, apiKey, providerSpecificData, proxyOptions = null) { + return getCodeBuddyUsage("codebuddy-intl", accessToken, apiKey, providerSpecificData, proxyOptions); +}