diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 3814e2b2f..6e6769be3 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -4,6 +4,7 @@ ### Added +- Added Ant Ling as a built-in OpenAI-compatible provider with Ling 2.6 and Ring 2.6 models. - Added MiniMax-M3 model to the `minimax` and `minimax-cn` direct providers, and removed the hardcoded context-window override that was masking models.dev values ([#5313](https://github.com/earendil-works/pi/issues/5313)). - Added NVIDIA NIM as a built-in OpenAI-compatible provider, exposing public NIM models that support tool use. diff --git a/packages/ai/README.md b/packages/ai/README.md index 76a987692..8f65bc727 100644 --- a/packages/ai/README.md +++ b/packages/ai/README.md @@ -51,6 +51,7 @@ Unified LLM API with automatic model discovery, provider configuration, token an ## Supported Providers - **OpenAI** +- **Ant Ling** - **Azure OpenAI (Responses)** - **OpenAI Codex** (ChatGPT Plus/Pro subscription, requires OAuth, see below) - **DeepSeek** @@ -939,7 +940,7 @@ interface OpenAICompletionsCompat { requiresAssistantAfterToolResult?: boolean; // Whether tool results must be followed by an assistant message (default: false) requiresThinkingAsText?: boolean; // Whether thinking blocks must be converted to text (default: false) requiresReasoningContentOnAssistantMessages?: boolean; // Whether all replayed assistant messages must include empty reasoning_content when reasoning is enabled (default: auto-detected for DeepSeek) - thinkingFormat?: 'openai' | 'openrouter' | 'deepseek' | 'together' | 'zai' | 'qwen' | 'qwen-chat-template'; // Format for reasoning param: 'openai' uses reasoning_effort, 'openrouter' uses reasoning: { effort }, 'deepseek' uses thinking: { type } plus reasoning_effort when supported, 'together' uses reasoning: { enabled } plus reasoning_effort when supported, 'zai' uses enable_thinking, 'qwen' uses enable_thinking, 'qwen-chat-template' uses chat_template_kwargs.enable_thinking (default: openai) + thinkingFormat?: 'openai' | 'openrouter' | 'deepseek' | 'together' | 'zai' | 'qwen' | 'qwen-chat-template' | 'string-thinking' | 'ant-ling'; // Format for reasoning param: 'openai' uses reasoning_effort, 'openrouter' uses reasoning: { effort }, 'deepseek' uses thinking: { type } plus reasoning_effort when supported, 'together' uses reasoning: { enabled } plus reasoning_effort when supported, 'zai' uses enable_thinking, 'qwen' uses enable_thinking, 'qwen-chat-template' uses chat_template_kwargs.enable_thinking, 'string-thinking' uses top-level thinking, 'ant-ling' uses reasoning: { effort } only for mapped efforts (default: openai) cacheControlFormat?: 'anthropic'; // Anthropic-style cache_control on system prompt, last tool, and last user/assistant text content openRouterRouting?: OpenRouterRouting; // OpenRouter routing preferences (default: {}) vercelGatewayRouting?: VercelGatewayRouting; // Vercel AI Gateway routing preferences (default: {}) @@ -1100,6 +1101,7 @@ In Node.js environments, you can set environment variables to avoid passing API | Provider | Environment Variable(s) | |----------|------------------------| | OpenAI | `OPENAI_API_KEY` | +| Ant Ling | `ANT_LING_API_KEY` | | Azure OpenAI | `AZURE_OPENAI_API_KEY` + `AZURE_OPENAI_BASE_URL` (e.g. `https://{resource}.openai.azure.com`) or `AZURE_OPENAI_RESOURCE_NAME`. Supports `*.openai.azure.com` and `*.cognitiveservices.azure.com`; root endpoints auto-normalize to `/openai/v1`. Optional: `AZURE_OPENAI_API_VERSION` (default `v1`), `AZURE_OPENAI_DEPLOYMENT_NAME_MAP`. | | Anthropic | `ANTHROPIC_API_KEY` or `ANTHROPIC_OAUTH_TOKEN` | | DeepSeek | `DEEPSEEK_API_KEY` | diff --git a/packages/ai/scripts/generate-models.ts b/packages/ai/scripts/generate-models.ts index 93a664e23..c98f868cd 100644 --- a/packages/ai/scripts/generate-models.ts +++ b/packages/ai/scripts/generate-models.ts @@ -169,6 +169,15 @@ const DEEPSEEK_V4_THINKING_LEVEL_MAP = { xhigh: "max", } as const; +const ANT_LING_RING_THINKING_LEVEL_MAP = { + off: null, + minimal: null, + low: null, + medium: null, + high: "high", + xhigh: "xhigh", +} as const; + const OPENAI_RESPONSES_NONE_REASONING_MODELS = new Set([ "gpt-5.1", "gpt-5.2", @@ -330,6 +339,10 @@ function applyThinkingLevelMetadata(model: Model): void { // OpenCode Zen Grok Build reasons by default but rejects explicit reasoningEffort. mergeThinkingLevelMap(model, { off: null, minimal: null, low: null, medium: null }); } + if (model.provider === "ant-ling" && model.reasoning) { + // Ring reasons by default. Only high/xhigh have documented explicit effort controls. + mergeThinkingLevelMap(model, ANT_LING_RING_THINKING_LEVEL_MAP); + } } function getAnthropicMessagesCompat(provider: string, modelId: string): AnthropicMessagesCompat | undefined { @@ -1643,6 +1656,56 @@ async function generateModels() { ]; allModels.push(...deepseekV4Models); + const antLingCompat: OpenAICompletionsCompat = { + supportsStore: false, + supportsDeveloperRole: false, + supportsReasoningEffort: false, + maxTokensField: "max_tokens", + supportsLongCacheRetention: false, + }; + const antLingModels: Model<"openai-completions">[] = [ + { + id: "Ling-2.6-flash", + name: "Ling 2.6 Flash", + api: "openai-completions", + baseUrl: "https://api.ant-ling.com/v1", + provider: "ant-ling", + reasoning: false, + input: ["text"], + cost: { input: 0.01, output: 0.02, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 262144, + maxTokens: 65536, + compat: antLingCompat, + }, + { + id: "Ling-2.6-1T", + name: "Ling 2.6 1T", + api: "openai-completions", + baseUrl: "https://api.ant-ling.com/v1", + provider: "ant-ling", + reasoning: false, + input: ["text"], + cost: { input: 0.06, output: 0.25, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 262144, + maxTokens: 65536, + compat: antLingCompat, + }, + { + id: "Ring-2.6-1T", + name: "Ring 2.6 1T", + api: "openai-completions", + baseUrl: "https://api.ant-ling.com/v1", + provider: "ant-ling", + reasoning: true, + input: ["text"], + cost: { input: 0.06, output: 0.25, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 262144, + maxTokens: 65536, + compat: { ...antLingCompat, thinkingFormat: "ant-ling" }, + }, + ]; + allModels.push(...antLingModels); + for (const candidate of allModels) { if (candidate.api === "openai-completions" && candidate.id.includes("deepseek-v4")) { candidate.compat = { diff --git a/packages/ai/src/env-api-keys.ts b/packages/ai/src/env-api-keys.ts index 6321a03a1..510f3b387 100644 --- a/packages/ai/src/env-api-keys.ts +++ b/packages/ai/src/env-api-keys.ts @@ -99,6 +99,7 @@ function getApiKeyEnvVars(provider: string): readonly string[] | undefined { } const envMap: Record = { + "ant-ling": "ANT_LING_API_KEY", openai: "OPENAI_API_KEY", "azure-openai-responses": "AZURE_OPENAI_API_KEY", nvidia: "NVIDIA_API_KEY", diff --git a/packages/ai/src/models.generated.ts b/packages/ai/src/models.generated.ts index 97001d3a3..54acc9529 100644 --- a/packages/ai/src/models.generated.ts +++ b/packages/ai/src/models.generated.ts @@ -1552,6 +1552,63 @@ export const MODELS = { maxTokens: 101376, } satisfies Model<"bedrock-converse-stream">, }, + "ant-ling": { + "Ling-2.6-1T": { + id: "Ling-2.6-1T", + name: "Ling 2.6 1T", + api: "openai-completions", + provider: "ant-ling", + baseUrl: "https://api.ant-ling.com/v1", + compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsLongCacheRetention":false}, + reasoning: false, + input: ["text"], + cost: { + input: 0.06, + output: 0.25, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 262144, + maxTokens: 65536, + } satisfies Model<"openai-completions">, + "Ling-2.6-flash": { + id: "Ling-2.6-flash", + name: "Ling 2.6 Flash", + api: "openai-completions", + provider: "ant-ling", + baseUrl: "https://api.ant-ling.com/v1", + compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsLongCacheRetention":false}, + reasoning: false, + input: ["text"], + cost: { + input: 0.01, + output: 0.02, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 262144, + maxTokens: 65536, + } satisfies Model<"openai-completions">, + "Ring-2.6-1T": { + id: "Ring-2.6-1T", + name: "Ring 2.6 1T", + api: "openai-completions", + provider: "ant-ling", + baseUrl: "https://api.ant-ling.com/v1", + compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsLongCacheRetention":false,"thinkingFormat":"ant-ling"}, + reasoning: true, + thinkingLevelMap: {"off":null,"minimal":null,"low":null,"medium":null,"high":"high","xhigh":"xhigh"}, + input: ["text"], + cost: { + input: 0.06, + output: 0.25, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 262144, + maxTokens: 65536, + } satisfies Model<"openai-completions">, + }, "anthropic": { "claude-3-5-haiku-20241022": { id: "claude-3-5-haiku-20241022", diff --git a/packages/ai/src/providers/openai-completions.ts b/packages/ai/src/providers/openai-completions.ts index ae6d583e3..5093d3e88 100644 --- a/packages/ai/src/providers/openai-completions.ts +++ b/packages/ai/src/providers/openai-completions.ts @@ -578,6 +578,11 @@ function buildParams( } else if (model.thinkingLevelMap?.off !== null) { openRouterParams.reasoning = { effort: model.thinkingLevelMap?.off ?? "none" }; } + } else if (compat.thinkingFormat === "ant-ling" && model.reasoning && options?.reasoningEffort) { + const effort = model.thinkingLevelMap?.[options.reasoningEffort]; + if (typeof effort === "string") { + (params as typeof params & { reasoning?: { effort: string } }).reasoning = { effort }; + } } else if (compat.thinkingFormat === "together" && model.reasoning) { const togetherParams = params as Omit & { reasoning?: { enabled: boolean }; @@ -1079,6 +1084,7 @@ function detectCompat(model: Model<"openai-completions">): ResolvedOpenAIComplet const isCloudflareWorkersAI = provider === "cloudflare-workers-ai" || baseUrl.includes("api.cloudflare.com"); const isCloudflareAiGateway = provider === "cloudflare-ai-gateway" || baseUrl.includes("gateway.ai.cloudflare.com"); const isNvidia = provider === "nvidia" || baseUrl.includes("integrate.api.nvidia.com"); + const isAntLing = provider === "ant-ling" || baseUrl.includes("api.ant-ling.com"); const isNonStandard = isNvidia || @@ -1094,9 +1100,11 @@ function detectCompat(model: Model<"openai-completions">): ResolvedOpenAIComplet provider === "opencode" || baseUrl.includes("opencode.ai") || isCloudflareWorkersAI || - isCloudflareAiGateway; + isCloudflareAiGateway || + isAntLing; - const useMaxTokens = baseUrl.includes("chutes.ai") || isMoonshot || isCloudflareAiGateway || isTogether || isNvidia; + const useMaxTokens = + baseUrl.includes("chutes.ai") || isMoonshot || isCloudflareAiGateway || isTogether || isNvidia || isAntLing; const isGrok = provider === "xai" || baseUrl.includes("api.x.ai"); const isDeepSeek = provider === "deepseek" || baseUrl.includes("deepseek.com"); @@ -1107,7 +1115,8 @@ function detectCompat(model: Model<"openai-completions">): ResolvedOpenAIComplet return { supportsStore: !isNonStandard, supportsDeveloperRole: isOpenRouterDeveloperRoleModel || (!isNonStandard && !isOpenRouter), - supportsReasoningEffort: !isGrok && !isZai && !isMoonshot && !isTogether && !isCloudflareAiGateway && !isNvidia, + supportsReasoningEffort: + !isGrok && !isZai && !isMoonshot && !isTogether && !isCloudflareAiGateway && !isNvidia && !isAntLing, supportsUsageInStreaming: true, maxTokensField: useMaxTokens ? "max_tokens" : "max_completion_tokens", requiresToolResultName: false, @@ -1120,16 +1129,24 @@ function detectCompat(model: Model<"openai-completions">): ResolvedOpenAIComplet ? "zai" : isTogether ? "together" - : isOpenRouter - ? "openrouter" - : "openai", + : isAntLing + ? "ant-ling" + : isOpenRouter + ? "openrouter" + : "openai", openRouterRouting: {}, vercelGatewayRouting: {}, zaiToolStream: false, supportsStrictMode: !isMoonshot && !isTogether && !isCloudflareAiGateway && !isNvidia, cacheControlFormat, sendSessionAffinityHeaders: false, - supportsLongCacheRetention: !(isTogether || isCloudflareWorkersAI || isCloudflareAiGateway || isNvidia), + supportsLongCacheRetention: !( + isTogether || + isCloudflareWorkersAI || + isCloudflareAiGateway || + isNvidia || + isAntLing + ), }; } diff --git a/packages/ai/src/types.ts b/packages/ai/src/types.ts index 7a4487720..cbf136a63 100644 --- a/packages/ai/src/types.ts +++ b/packages/ai/src/types.ts @@ -22,6 +22,7 @@ export type ImagesApi = KnownImagesApi | (string & {}); export type KnownProvider = | "amazon-bedrock" + | "ant-ling" | "anthropic" | "google" | "google-vertex" @@ -390,7 +391,7 @@ export interface OpenAICompletionsCompat { requiresThinkingAsText?: boolean; /** Whether all replayed assistant messages must include an empty reasoning_content field when reasoning is enabled. Default: auto-detected from URL. */ requiresReasoningContentOnAssistantMessages?: boolean; - /** Format for reasoning/thinking parameter. "openai" uses reasoning_effort, "openrouter" uses reasoning: { effort }, "deepseek" uses thinking: { type } plus reasoning_effort when supported, "together" uses reasoning: { enabled } plus reasoning_effort when supported, "zai" uses top-level enable_thinking: boolean, "qwen" uses top-level enable_thinking: boolean, "qwen-chat-template" uses chat_template_kwargs.enable_thinking, and "string-thinking" uses top-level thinking: string. Default: "openai". */ + /** Format for reasoning/thinking parameter. "openai" uses reasoning_effort, "openrouter" uses reasoning: { effort }, "deepseek" uses thinking: { type } plus reasoning_effort when supported, "together" uses reasoning: { enabled } plus reasoning_effort when supported, "zai" uses top-level enable_thinking: boolean, "qwen" uses top-level enable_thinking: boolean, "qwen-chat-template" uses chat_template_kwargs.enable_thinking, "string-thinking" uses top-level thinking: string, and "ant-ling" uses reasoning: { effort } only when the mapped effort is non-null. Default: "openai". */ thinkingFormat?: | "openai" | "openrouter" @@ -399,7 +400,8 @@ export interface OpenAICompletionsCompat { | "zai" | "qwen" | "qwen-chat-template" - | "string-thinking"; + | "string-thinking" + | "ant-ling"; /** OpenRouter-specific routing preferences. Only used when baseUrl points to OpenRouter. */ openRouterRouting?: OpenRouterRouting; /** Vercel AI Gateway routing preferences. Only used when baseUrl points to Vercel AI Gateway. */ diff --git a/packages/ai/test/openai-completions-tool-choice.test.ts b/packages/ai/test/openai-completions-tool-choice.test.ts index c06ac344e..be6419d61 100644 --- a/packages/ai/test/openai-completions-tool-choice.test.ts +++ b/packages/ai/test/openai-completions-tool-choice.test.ts @@ -2,7 +2,7 @@ import { Type } from "typebox"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { getModel } from "../src/models.ts"; import { convertMessages } from "../src/providers/openai-completions.ts"; -import { streamSimple } from "../src/stream.ts"; +import { stream, streamSimple } from "../src/stream.ts"; import type { AssistantMessage, Model, Tool, ToolResultMessage } from "../src/types.ts"; const mockState = vi.hoisted(() => ({ @@ -1295,4 +1295,95 @@ describe("openai-completions tool_choice", () => { expect(params.reasoning).toEqual({ effort: "high" }); expect(params.reasoning_effort).toBeUndefined(); }); + + it("uses Ant Ling compatibility metadata", async () => { + const model = getModel("ant-ling", "Ring-2.6-1T")!; + let payload: unknown; + + expect(model.compat).toMatchObject({ + supportsStore: false, + supportsDeveloperRole: false, + supportsReasoningEffort: false, + maxTokensField: "max_tokens", + thinkingFormat: "ant-ling", + supportsLongCacheRetention: false, + }); + expect(model.compat?.supportsStrictMode).toBeUndefined(); + expect(model.compat?.requiresReasoningContentOnAssistantMessages).toBeUndefined(); + + await streamSimple( + model, + { + systemPrompt: "Follow instructions.", + messages: [{ role: "user", content: "Hi", timestamp: Date.now() }], + }, + { + apiKey: "test", + maxTokens: 123, + reasoning: "high", + cacheRetention: "long", + sessionId: "ant-ling-session", + onPayload: (params: unknown) => { + payload = params; + }, + }, + ).result(); + + const params = (payload ?? mockState.lastParams) as { + max_tokens?: number; + max_completion_tokens?: number; + messages?: Array<{ role?: string }>; + reasoning?: { effort?: string }; + reasoning_effort?: string; + store?: boolean; + prompt_cache_key?: string; + prompt_cache_retention?: string; + }; + expect(params.max_tokens).toBe(123); + expect(params.max_completion_tokens).toBeUndefined(); + expect(params.messages?.[0]?.role).toBe("system"); + expect(params.reasoning).toEqual({ effort: "high" }); + expect(params.reasoning_effort).toBeUndefined(); + expect(params.store).toBeUndefined(); + expect(params.prompt_cache_key).toBeUndefined(); + expect(params.prompt_cache_retention).toBeUndefined(); + }); + + it("omits Ant Ling reasoning for unmapped direct reasoning efforts and non-reasoning models", async () => { + const ring = getModel("ant-ling", "Ring-2.6-1T")!; + let payload: unknown; + + await stream( + ring, + { + messages: [{ role: "user", content: "Hi", timestamp: Date.now() }], + }, + { + apiKey: "test", + reasoningEffort: "medium", + onPayload: (params: unknown) => { + payload = params; + }, + }, + ).result(); + + expect((payload ?? mockState.lastParams) as { reasoning?: unknown }).not.toHaveProperty("reasoning"); + + const ling = getModel("ant-ling", "Ling-2.6-flash")!; + await streamSimple( + ling, + { + messages: [{ role: "user", content: "Hi", timestamp: Date.now() }], + }, + { + apiKey: "test", + reasoning: "high", + onPayload: (params: unknown) => { + payload = params; + }, + }, + ).result(); + + expect((payload ?? mockState.lastParams) as { reasoning?: unknown }).not.toHaveProperty("reasoning"); + }); }); diff --git a/packages/ai/test/stream.test.ts b/packages/ai/test/stream.test.ts index 1693f4066..9a70b6c28 100644 --- a/packages/ai/test/stream.test.ts +++ b/packages/ai/test/stream.test.ts @@ -1169,6 +1169,27 @@ describe("Generate E2E Tests", () => { }, ); + describe.skipIf(!process.env.ANT_LING_API_KEY)("Ant Ling Provider (Ling 2.6 Flash via OpenAI Completions)", () => { + const llm = getModel("ant-ling", "Ling-2.6-flash"); + + it("should complete basic text generation", { retry: 3 }, async () => { + await basicTextGeneration(llm); + }); + + it("should handle tool calling", { retry: 3 }, async () => { + await handleToolCall(llm); + }); + + it("should handle streaming", { retry: 3 }, async () => { + await handleStreaming(llm); + }); + + it("should handle thinking mode", { retry: 3 }, async () => { + const ringModel = getModel("ant-ling", "Ring-2.6-1T"); + await handleThinking(ringModel, { reasoningEffort: "high" }); + }); + }); + // ========================================================================= // OAuth-based providers (credentials from ~/.pi/agent/oauth.json) // Tokens are resolved at module level (see oauthTokens above) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 82c509e41..46eebe64b 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -4,6 +4,7 @@ ### Added +- Added Ant Ling provider selection and setup documentation. - Added NVIDIA NIM provider selection, setup documentation, and direct NIM request attribution headers. - Added `ctx.mode` to extension contexts so extensions can distinguish TUI, RPC, JSON, and print mode. - Added `ctx.getSystemPromptOptions()` for extension commands to inspect the current base system prompt inputs. diff --git a/packages/coding-agent/README.md b/packages/coding-agent/README.md index 585a93a0c..8ef336c79 100644 --- a/packages/coding-agent/README.md +++ b/packages/coding-agent/README.md @@ -110,6 +110,7 @@ For each built-in provider, pi maintains a list of tool-capable models, updated **API keys:** - Anthropic +- Ant Ling - OpenAI - Azure OpenAI - DeepSeek diff --git a/packages/coding-agent/docs/providers.md b/packages/coding-agent/docs/providers.md index e7a77c5aa..0ecf439df 100644 --- a/packages/coding-agent/docs/providers.md +++ b/packages/coding-agent/docs/providers.md @@ -49,6 +49,7 @@ pi | Provider | Environment Variable | `auth.json` key | |----------|----------------------|------------------| | Anthropic | `ANTHROPIC_API_KEY` | `anthropic` | +| Ant Ling | `ANT_LING_API_KEY` | `ant-ling` | | Azure OpenAI Responses | `AZURE_OPENAI_API_KEY` | `azure-openai-responses` | | OpenAI | `OPENAI_API_KEY` | `openai` | | DeepSeek | `DEEPSEEK_API_KEY` | `deepseek` | @@ -85,6 +86,7 @@ Store credentials in `~/.pi/agent/auth.json`: ```json { "anthropic": { "type": "api_key", "key": "sk-ant-..." }, + "ant-ling": { "type": "api_key", "key": "..." }, "openai": { "type": "api_key", "key": "sk-..." }, "deepseek": { "type": "api_key", "key": "sk-..." }, "nvidia": { "type": "api_key", "key": "nvapi-..." }, diff --git a/packages/coding-agent/src/cli/args.ts b/packages/coding-agent/src/cli/args.ts index 8156bd03f..458bd43ee 100644 --- a/packages/coding-agent/src/cli/args.ts +++ b/packages/coding-agent/src/cli/args.ts @@ -328,6 +328,7 @@ ${chalk.bold("Examples:")} ${chalk.bold("Environment Variables:")} ANTHROPIC_API_KEY - Anthropic Claude API key ANTHROPIC_OAUTH_TOKEN - Anthropic OAuth token (alternative to API key) + ANT_LING_API_KEY - Ant Ling API key OPENAI_API_KEY - OpenAI GPT API key AZURE_OPENAI_API_KEY - Azure OpenAI API key AZURE_OPENAI_BASE_URL - Azure OpenAI/Cognitive Services base URL (e.g. https://{resource}.openai.azure.com) diff --git a/packages/coding-agent/src/core/model-resolver.ts b/packages/coding-agent/src/core/model-resolver.ts index e3af076da..a00471c9c 100644 --- a/packages/coding-agent/src/core/model-resolver.ts +++ b/packages/coding-agent/src/core/model-resolver.ts @@ -13,6 +13,7 @@ import type { ModelRegistry } from "./model-registry.ts"; /** Default model IDs for each known provider */ export const defaultModelPerProvider: Record = { "amazon-bedrock": "us.anthropic.claude-opus-4-6-v1", + "ant-ling": "Ring-2.6-1T", anthropic: "claude-opus-4-8", openai: "gpt-5.4", "azure-openai-responses": "gpt-5.4", diff --git a/packages/coding-agent/src/core/provider-display-names.ts b/packages/coding-agent/src/core/provider-display-names.ts index 789f31530..3481e6aef 100644 --- a/packages/coding-agent/src/core/provider-display-names.ts +++ b/packages/coding-agent/src/core/provider-display-names.ts @@ -1,6 +1,7 @@ export const BUILT_IN_PROVIDER_DISPLAY_NAMES: Record = { anthropic: "Anthropic", "amazon-bedrock": "Amazon Bedrock", + "ant-ling": "Ant Ling", "azure-openai-responses": "Azure OpenAI Responses", cerebras: "Cerebras", "cloudflare-ai-gateway": "Cloudflare AI Gateway", diff --git a/packages/coding-agent/test/model-resolver.test.ts b/packages/coding-agent/test/model-resolver.test.ts index 7339200bc..0ac549394 100644 --- a/packages/coding-agent/test/model-resolver.test.ts +++ b/packages/coding-agent/test/model-resolver.test.ts @@ -378,11 +378,12 @@ describe("default model selection", () => { expect(defaultModelPerProvider["openai-codex"]).toBe("gpt-5.5"); }); - test("zai, minimax, and cerebras defaults track current models", () => { + test("zai, minimax, cerebras, and ant-ling defaults track current models", () => { expect(defaultModelPerProvider.zai).toBe("glm-5.1"); expect(defaultModelPerProvider.minimax).toBe("MiniMax-M2.7"); expect(defaultModelPerProvider["minimax-cn"]).toBe("MiniMax-M2.7"); expect(defaultModelPerProvider.cerebras).toBe("zai-glm-4.7"); + expect(defaultModelPerProvider["ant-ling"]).toBe("Ring-2.6-1T"); }); test("ai-gateway default tracks current model", () => {