mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-07-09 17:28:45 +00:00
Add Ant Ling provider
This commit is contained in:
parent
6cb23f9b5d
commit
25a4a8ed1e
16 changed files with 275 additions and 12 deletions
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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` |
|
||||
|
|
|
|||
|
|
@ -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<any>): 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 = {
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ function getApiKeyEnvVars(provider: string): readonly string[] | undefined {
|
|||
}
|
||||
|
||||
const envMap: Record<string, string> = {
|
||||
"ant-ling": "ANT_LING_API_KEY",
|
||||
openai: "OPENAI_API_KEY",
|
||||
"azure-openai-responses": "AZURE_OPENAI_API_KEY",
|
||||
nvidia: "NVIDIA_API_KEY",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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<typeof params, "reasoning_effort"> & {
|
||||
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
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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-..." },
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import type { ModelRegistry } from "./model-registry.ts";
|
|||
/** Default model IDs for each known provider */
|
||||
export const defaultModelPerProvider: Record<KnownProvider, string> = {
|
||||
"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",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
export const BUILT_IN_PROVIDER_DISPLAY_NAMES: Record<string, string> = {
|
||||
anthropic: "Anthropic",
|
||||
"amazon-bedrock": "Amazon Bedrock",
|
||||
"ant-ling": "Ant Ling",
|
||||
"azure-openai-responses": "Azure OpenAI Responses",
|
||||
cerebras: "Cerebras",
|
||||
"cloudflare-ai-gateway": "Cloudflare AI Gateway",
|
||||
|
|
|
|||
|
|
@ -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", () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue