diff --git a/packages/pi-ai/src/env-api-keys.ts b/packages/pi-ai/src/env-api-keys.ts index 749e074f9..55188bae9 100644 --- a/packages/pi-ai/src/env-api-keys.ts +++ b/packages/pi-ai/src/env-api-keys.ts @@ -122,6 +122,7 @@ export function getEnvApiKey(provider: any): string | undefined { opencode: "OPENCODE_API_KEY", "opencode-go": "OPENCODE_API_KEY", "kimi-coding": "KIMI_API_KEY", + "alibaba-coding-plan": "ALIBABA_API_KEY", }; const envVar = envMap[provider]; diff --git a/packages/pi-ai/src/models.generated.ts b/packages/pi-ai/src/models.generated.ts index 02fd5c121..9f319a0a0 100644 --- a/packages/pi-ai/src/models.generated.ts +++ b/packages/pi-ai/src/models.generated.ts @@ -13384,4 +13384,142 @@ export const MODELS = { maxTokens: 131072, } satisfies Model<"openai-completions">, }, + "alibaba-coding-plan": { + "qwen3.5-plus": { + id: "qwen3.5-plus", + name: "Qwen3.5 Plus", + api: "anthropic-messages", + provider: "alibaba-coding-plan", + baseUrl: "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic", + reasoning: true, + input: ["text", "image"], + cost: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 1000000, + maxTokens: 65536, + } satisfies Model<"anthropic-messages">, + "qwen3-max-2026-01-23": { + id: "qwen3-max-2026-01-23", + name: "Qwen3 Max 2026-01-23", + api: "anthropic-messages", + provider: "alibaba-coding-plan", + baseUrl: "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic", + reasoning: false, + input: ["text"], + cost: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 262144, + maxTokens: 32768, + } satisfies Model<"anthropic-messages">, + "qwen3-coder-next": { + id: "qwen3-coder-next", + name: "Qwen3 Coder Next", + api: "anthropic-messages", + provider: "alibaba-coding-plan", + baseUrl: "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic", + reasoning: false, + input: ["text"], + cost: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 262144, + maxTokens: 65536, + } satisfies Model<"anthropic-messages">, + "qwen3-coder-plus": { + id: "qwen3-coder-plus", + name: "Qwen3 Coder Plus", + api: "anthropic-messages", + provider: "alibaba-coding-plan", + baseUrl: "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic", + reasoning: false, + input: ["text"], + cost: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 1000000, + maxTokens: 65536, + } satisfies Model<"anthropic-messages">, + "MiniMax-M2.5": { + id: "MiniMax-M2.5", + name: "MiniMax M2.5", + api: "anthropic-messages", + provider: "alibaba-coding-plan", + baseUrl: "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic", + reasoning: true, + input: ["text"], + cost: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 196608, + maxTokens: 24576, + } satisfies Model<"anthropic-messages">, + "glm-5": { + id: "glm-5", + name: "GLM-5", + api: "anthropic-messages", + provider: "alibaba-coding-plan", + baseUrl: "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic", + reasoning: true, + input: ["text"], + cost: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 202752, + maxTokens: 16384, + } satisfies Model<"anthropic-messages">, + "glm-4.7": { + id: "glm-4.7", + name: "GLM-4.7", + api: "anthropic-messages", + provider: "alibaba-coding-plan", + baseUrl: "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic", + reasoning: true, + input: ["text"], + cost: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 202752, + maxTokens: 16384, + } satisfies Model<"anthropic-messages">, + "kimi-k2.5": { + id: "kimi-k2.5", + name: "Kimi K2.5", + api: "anthropic-messages", + provider: "alibaba-coding-plan", + baseUrl: "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic", + reasoning: true, + input: ["text", "image"], + cost: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 262144, + maxTokens: 32768, + } satisfies Model<"anthropic-messages">, + }, } as const; diff --git a/packages/pi-ai/src/providers/anthropic.ts b/packages/pi-ai/src/providers/anthropic.ts index e4f49de58..59e5c8f83 100644 --- a/packages/pi-ai/src/providers/anthropic.ts +++ b/packages/pi-ai/src/providers/anthropic.ts @@ -452,6 +452,9 @@ export const streamAnthropic: StreamFunction<"anthropic-messages", AnthropicOpti for (const block of output.content) delete (block as any).index; output.stopReason = options?.signal?.aborted ? "aborted" : "error"; output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error); + if (model.provider === "alibaba-coding-plan") { + output.errorMessage = `[alibaba-coding-plan] ${output.errorMessage}`; + } if (error instanceof Anthropic.APIError && error.headers) { const retryAfterMs = extractRetryAfterMs(error.headers, error.message); if (retryAfterMs !== undefined) { @@ -583,8 +586,10 @@ function createClient( return { client, isOAuthToken: false }; } - const betaFeatures = ["fine-grained-tool-streaming-2025-05-14"]; - if (needsInterleavedBeta) { + // Skip beta headers for providers that don't support them (e.g., Alibaba Coding Plan) + const skipBetaHeaders = model.provider === "alibaba-coding-plan"; + const betaFeatures = skipBetaHeaders ? [] : ["fine-grained-tool-streaming-2025-05-14"]; + if (needsInterleavedBeta && !skipBetaHeaders) { betaFeatures.push("interleaved-thinking-2025-05-14"); } @@ -599,7 +604,7 @@ function createClient( { accept: "application/json", "anthropic-dangerous-direct-browser-access": "true", - "anthropic-beta": `claude-code-20250219,oauth-2025-04-20,${betaFeatures.join(",")}`, + ...(betaFeatures.length > 0 ? { "anthropic-beta": `claude-code-20250219,oauth-2025-04-20,${betaFeatures.join(",")}` } : {}), "user-agent": `claude-cli/${claudeCodeVersion}`, "x-app": "cli", }, @@ -612,15 +617,18 @@ function createClient( } // API key auth + // Alibaba Coding Plan uses Bearer token auth instead of x-api-key + const isAlibabaProvider = model.provider === "alibaba-coding-plan"; const client = new Anthropic({ - apiKey, + apiKey: isAlibabaProvider ? null : apiKey, + authToken: isAlibabaProvider ? apiKey : undefined, baseURL: model.baseUrl, dangerouslyAllowBrowser: true, defaultHeaders: mergeHeaders( { accept: "application/json", "anthropic-dangerous-direct-browser-access": "true", - "anthropic-beta": betaFeatures.join(","), + ...(betaFeatures.length > 0 ? { "anthropic-beta": betaFeatures.join(",") } : {}), }, model.headers, optionsHeaders, diff --git a/packages/pi-ai/src/types.ts b/packages/pi-ai/src/types.ts index 96f64c08f..0f1bb08a1 100644 --- a/packages/pi-ai/src/types.ts +++ b/packages/pi-ai/src/types.ts @@ -39,7 +39,8 @@ export type KnownProvider = | "huggingface" | "opencode" | "opencode-go" - | "kimi-coding"; + | "kimi-coding" + | "alibaba-coding-plan"; export type Provider = KnownProvider | string; export type ThinkingLevel = "minimal" | "low" | "medium" | "high" | "xhigh"; diff --git a/packages/pi-coding-agent/src/core/model-resolver.ts b/packages/pi-coding-agent/src/core/model-resolver.ts index a09ff85cd..8342915f9 100644 --- a/packages/pi-coding-agent/src/core/model-resolver.ts +++ b/packages/pi-coding-agent/src/core/model-resolver.ts @@ -35,6 +35,7 @@ export const defaultModelPerProvider: Record = { opencode: "claude-opus-4-6", "opencode-go": "kimi-k2.5", "kimi-coding": "kimi-k2-thinking", + "alibaba-coding-plan": "qwen3.5-plus", }; export interface ScopedModel {