@oh-my-pi/pi-ai 15.9.0 → 15.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/types/provider-models/openai-compat.d.ts +28 -1
- package/dist/types/providers/openai-completions-compat.d.ts +3 -1
- package/dist/types/providers/vision-guard.d.ts +13 -1
- package/dist/types/types.d.ts +7 -1
- package/dist/types/utils/oauth/types.d.ts +1 -1
- package/dist/types/utils/oauth/xiaomi.d.ts +10 -4
- package/package.json +2 -2
- package/src/auth-storage.ts +16 -1
- package/src/models.json +96 -13
- package/src/provider-models/descriptors.ts +9 -0
- package/src/provider-models/openai-compat.ts +69 -17
- package/src/providers/anthropic.ts +0 -1
- package/src/providers/openai-completions-compat.ts +31 -11
- package/src/providers/openai-completions.ts +14 -11
- package/src/providers/vision-guard.ts +22 -1
- package/src/stream.ts +3 -0
- package/src/types.ts +9 -0
- package/src/usage/claude.ts +1 -1
- package/src/utils/oauth/index.ts +18 -0
- package/src/utils/oauth/types.ts +3 -0
- package/src/utils/oauth/xiaomi.ts +72 -17
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [15.9.1] - 2026-06-04
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added regional Xiaomi Token Plan login/provider entries (`xiaomi-token-plan-sgp`, `xiaomi-token-plan-ams`, `xiaomi-token-plan-cn`) so `omp login` can store token-plan keys against the selected region. ([#1846](https://github.com/can1357/oh-my-pi/issues/1846))
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Removed the `context-1m-2025-08-07` (1M long-context) beta from the Anthropic agent request headers, the OAuth model-discovery header, and the Claude usage-API header. Sending it caused subscription/OAuth requests without long-context credits to fail with `429 Usage credits are required for long context requests`, breaking Sonnet. The remaining betas are unchanged.
|
|
14
|
+
- Fixed Kimi K2.x `maxTokens` on Fireworks and Fire Pass (`fireworks/kimi-k2.5`, `fireworks/kimi-k2.6`, `firepass/kimi-k2.6-turbo`) being inherited from Fireworks `/v1/models` discovery (`max_completion_tokens: 65536`) rather than the published Kimi-on-Fireworks output budget, which let callers (and the openai-completions default-injection safety net) ship a budget the router cannot honor and made runaway reasoning traces more likely. The Fireworks resolver now clamps every Kimi K2.x id (public catalog ids and the canonical `accounts/fireworks/{models,routers}/kimi-k2…` wire form) to 32,768 output tokens, and the generator applies the same cap as a post-processing safety net so the `firepass` static fallback and the bundled `fireworks` entries stay in sync across regens. ([#1849](https://github.com/can1357/oh-my-pi/issues/1849))
|
|
15
|
+
- Fixed Xiaomi Token Plan MiMo OpenAI-compatible tool-call continuations omitting required `reasoning_content` replay. ([#1846](https://github.com/can1357/oh-my-pi/issues/1846))
|
|
16
|
+
- Fixed Anthropic prompt caching for OpenAI-compatible Claude proxies by honoring `compat.cacheControlFormat: "anthropic"` outside OpenRouter. ([#1845](https://github.com/can1357/oh-my-pi/issues/1845))
|
|
17
|
+
- Fixed Moonshot Kimi K2.6 silently pausing for many seconds between tool calls because the server discarded the `reasoning_content` that omp was already sending with every assistant tool-call replay. The K2.6 `thinking` parameter takes an extra `keep` field whose default (`null`) ignores historical reasoning, so K2.6 had to re-derive its full chain-of-thought from the user prompt on every iteration of the agent loop. The Moonshot direct (`api.moonshot.ai`) and Kimi Code (`api.kimi.com`) wire bodies now send `thinking: { type: "enabled", keep: "all" }` for `kimi-k2.6` requests with reasoning enabled, matching Moonshot's documented best practice for multi-step tool-calling agents. The flag is gated on the K2.6 id and the two native hosts because earlier Moonshot models (K2.5 and below) 400 on the unknown field and every Kimi gateway (OpenRouter, OpenCode, Kilo, Fireworks, …) speaks its own thinking shape. ([#1838](https://github.com/can1357/oh-my-pi/issues/1838))
|
|
18
|
+
- Fixed Alibaba DashScope (Bailian) compatible-mode endpoint `400 InternalError.Algo.InvalidParameter: The provided messages input is invalid. The error info is [Unexpected item type in content.]` when a screenshot or other image-producing tool result was folded into a known text-only Qwen turn (e.g. `qwen3.7-max`, `qwen-max`, `qwen3-coder-*`) hosted at `dashscope.aliyuncs.com/compatible-mode/v1`. `convertMessages` in `openai-completions` no longer forwards `image_url` content parts for those text-only id families even when a misconfigured custom provider claims `input: ["text", "image"]`; multimodal compatible-mode ids such as `qwen3.7-plus` and `qwen-vl-max` still rely on the catalog `input` field. The tool-result branch and the user-content branch both fall back to the standard `[image omitted: model does not support vision]` placeholder for text-only ids so the model still sees the attachment intent. ([#1859](https://github.com/can1357/oh-my-pi/issues/1859))
|
|
19
|
+
|
|
5
20
|
## [15.9.0] - 2026-06-04
|
|
6
21
|
|
|
7
22
|
### Fixed
|
|
@@ -18,6 +33,7 @@
|
|
|
18
33
|
- Fixed MiniMax Coding Plan China login opening the international `platform.minimax.io` subscription page instead of the China `platform.minimaxi.com` page.
|
|
19
34
|
|
|
20
35
|
## [15.8.0] - 2026-06-02
|
|
36
|
+
|
|
21
37
|
### Added
|
|
22
38
|
|
|
23
39
|
- Added `AnthropicMessagesClient` and related Anthropic wire types/errors via `anthropic-client` export so callers can build a standalone Anthropic Messages client without depending on `@anthropic-ai/sdk`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ModelManagerOptions } from "../model-manager";
|
|
2
|
-
import type { Api, Model } from "../types";
|
|
2
|
+
import type { Api, Model, Provider } from "../types";
|
|
3
3
|
export interface ModelsDevModel {
|
|
4
4
|
id?: string;
|
|
5
5
|
name?: string;
|
|
@@ -110,6 +110,27 @@ export interface ZhipuCodingPlanModelManagerConfig {
|
|
|
110
110
|
baseUrl?: string;
|
|
111
111
|
}
|
|
112
112
|
export declare function zhipuCodingPlanModelManagerOptions(config?: ZhipuCodingPlanModelManagerConfig): ModelManagerOptions<"openai-completions">;
|
|
113
|
+
/**
|
|
114
|
+
* Fireworks-published cap for the Kimi K2 family. Fireworks' `/v1/models`
|
|
115
|
+
* envelope generically reports `max_completion_tokens: 65536` for every Kimi
|
|
116
|
+
* deployment, but Kimi K2 (instruct / thinking / turbo) on Fireworks is
|
|
117
|
+
* documented to ship long reasoning traces that should be bounded — capping
|
|
118
|
+
* at 32,768 prevents handing callers a budget the router cannot honor.
|
|
119
|
+
* See https://github.com/can1357/oh-my-pi/issues/1849.
|
|
120
|
+
*/
|
|
121
|
+
export declare const FIREWORKS_KIMI_MAX_TOKENS = 32768;
|
|
122
|
+
/**
|
|
123
|
+
* Returns true for any Kimi K2.x public model id served by Fireworks-backed
|
|
124
|
+
* providers (`fireworks` direct, `firepass` router). Matches both the public
|
|
125
|
+
* catalog id (`kimi-k2.5`, `kimi-k2.6`, `kimi-k2.6-turbo`) and the canonical
|
|
126
|
+
* Fireworks wire id (`accounts/fireworks/{models,routers}/kimi-k2…`).
|
|
127
|
+
*/
|
|
128
|
+
export declare function isFireworksKimiK2ModelId(modelId: string): boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Clamp the Kimi K2 family's `maxTokens` to {@link FIREWORKS_KIMI_MAX_TOKENS}
|
|
131
|
+
* on Fireworks-backed providers, leaving every other model untouched.
|
|
132
|
+
*/
|
|
133
|
+
export declare function clampFireworksKimiMaxTokens(modelId: string, candidate: number): number;
|
|
113
134
|
export interface FireworksModelManagerConfig {
|
|
114
135
|
apiKey?: string;
|
|
115
136
|
baseUrl?: string;
|
|
@@ -219,10 +240,16 @@ export interface CloudflareAiGatewayModelManagerConfig {
|
|
|
219
240
|
baseUrl?: string;
|
|
220
241
|
}
|
|
221
242
|
export declare function cloudflareAiGatewayModelManagerOptions(config?: CloudflareAiGatewayModelManagerConfig): ModelManagerOptions<"anthropic-messages">;
|
|
243
|
+
/** Region codes for Xiaomi Token Plan clusters exposed as separate login providers. */
|
|
244
|
+
export type XiaomiTokenPlanRegion = "sgp" | "ams" | "cn";
|
|
245
|
+
/** Configures Xiaomi standard or regional Token Plan OpenAI-compatible model discovery. */
|
|
222
246
|
export interface XiaomiModelManagerConfig {
|
|
223
247
|
apiKey?: string;
|
|
224
248
|
baseUrl?: string;
|
|
249
|
+
providerId?: Provider;
|
|
250
|
+
tokenPlanRegion?: XiaomiTokenPlanRegion;
|
|
225
251
|
}
|
|
252
|
+
/** Builds a Xiaomi model manager, preserving Token Plan region provider ids during discovery. */
|
|
226
253
|
export declare function xiaomiModelManagerOptions(config?: XiaomiModelManagerConfig): ModelManagerOptions<"openai-completions">;
|
|
227
254
|
export interface LiteLLMModelManagerConfig {
|
|
228
255
|
apiKey?: string;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { Model, OpenAICompat } from "../types";
|
|
2
2
|
type ResolvedToolStrictMode = NonNullable<OpenAICompat["toolStrictMode"]> | "mixed";
|
|
3
|
-
export type ResolvedOpenAICompat = Required<Omit<OpenAICompat, "openRouterRouting" | "vercelGatewayRouting" | "extraBody" | "toolStrictMode">> & {
|
|
3
|
+
export type ResolvedOpenAICompat = Required<Omit<OpenAICompat, "openRouterRouting" | "vercelGatewayRouting" | "extraBody" | "toolStrictMode" | "cacheControlFormat" | "thinkingKeep">> & {
|
|
4
4
|
openRouterRouting?: OpenAICompat["openRouterRouting"];
|
|
5
5
|
vercelGatewayRouting?: OpenAICompat["vercelGatewayRouting"];
|
|
6
6
|
extraBody?: OpenAICompat["extraBody"];
|
|
7
|
+
cacheControlFormat?: OpenAICompat["cacheControlFormat"];
|
|
8
|
+
thinkingKeep?: OpenAICompat["thinkingKeep"];
|
|
7
9
|
toolStrictMode: ResolvedToolStrictMode;
|
|
8
10
|
};
|
|
9
11
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ImageContent, TextContent } from "../types";
|
|
1
|
+
import type { ImageContent, Model, TextContent } from "../types";
|
|
2
2
|
export declare const NON_VISION_IMAGE_PLACEHOLDER = "[image omitted: model does not support vision]";
|
|
3
3
|
export declare function partitionVisionContent(content: ReadonlyArray<TextContent | ImageContent>, supportsImages: boolean): {
|
|
4
4
|
textBlocks: TextContent[];
|
|
@@ -6,3 +6,15 @@ export declare function partitionVisionContent(content: ReadonlyArray<TextConten
|
|
|
6
6
|
omittedImages: boolean;
|
|
7
7
|
};
|
|
8
8
|
export declare function joinTextWithImagePlaceholder(text: string, omittedImages: boolean): string;
|
|
9
|
+
/**
|
|
10
|
+
* Detect known text-only Qwen models served via Alibaba DashScope's consumer
|
|
11
|
+
* `compatible-mode` endpoint that the upstream chat-completions API rejects
|
|
12
|
+
* multimodal content arrays for. The compatible-mode endpoint also serves
|
|
13
|
+
* multimodal Qwen SKUs without `vl` in the id (e.g. `qwen3.7-plus`), so this
|
|
14
|
+
* guard only covers families verified to be text-only for issue #1859:
|
|
15
|
+
* `qwen*-max` and `qwen*-coder*`.
|
|
16
|
+
*
|
|
17
|
+
* Used as a defensive override in `convertMessages` so a misconfigured custom
|
|
18
|
+
* provider (issue #1859) can't drive the request into an unrecoverable 400.
|
|
19
|
+
*/
|
|
20
|
+
export declare function isDashscopeCompatibleModeTextOnlyQwen(model: Model<"openai-completions">): boolean;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ export interface ThinkingConfig {
|
|
|
48
48
|
/** Provider-specific transport used to encode the selected effort. */
|
|
49
49
|
mode: ThinkingControlMode;
|
|
50
50
|
}
|
|
51
|
-
export type KnownProvider = "alibaba-coding-plan" | "amazon-bedrock" | "anthropic" | "google" | "google-gemini-cli" | "google-antigravity" | "google-vertex" | "openai" | "openai-codex" | "kimi-code" | "minimax-code" | "minimax-code-cn" | "github-copilot" | "fireworks" | "firepass" | "gitlab-duo" | "cursor" | "deepseek" | "xai" | "xai-oauth" | "groq" | "cerebras" | "openrouter" | "kilo" | "vercel-ai-gateway" | "zai" | "zhipu-coding-plan" | "mistral" | "minimax" | "opencode-go" | "opencode-zen" | "synthetic" | "cloudflare-ai-gateway" | "huggingface" | "litellm" | "moonshot" | "nvidia" | "nanogpt" | "ollama" | "ollama-cloud" | "qianfan" | "qwen-portal" | "together" | "venice" | "vllm" | "xiaomi" | "wafer-pass" | "wafer-serverless" | "zenmux" | "lm-studio";
|
|
51
|
+
export type KnownProvider = "alibaba-coding-plan" | "amazon-bedrock" | "anthropic" | "google" | "google-gemini-cli" | "google-antigravity" | "google-vertex" | "openai" | "openai-codex" | "kimi-code" | "minimax-code" | "minimax-code-cn" | "github-copilot" | "fireworks" | "firepass" | "gitlab-duo" | "cursor" | "deepseek" | "xai" | "xai-oauth" | "groq" | "cerebras" | "openrouter" | "kilo" | "vercel-ai-gateway" | "zai" | "zhipu-coding-plan" | "mistral" | "minimax" | "opencode-go" | "opencode-zen" | "synthetic" | "cloudflare-ai-gateway" | "huggingface" | "litellm" | "moonshot" | "nvidia" | "nanogpt" | "ollama" | "ollama-cloud" | "qianfan" | "qwen-portal" | "together" | "venice" | "vllm" | "xiaomi" | "xiaomi-token-plan-sgp" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "wafer-pass" | "wafer-serverless" | "zenmux" | "lm-studio";
|
|
52
52
|
export type Provider = KnownProvider | string;
|
|
53
53
|
import type { Effort } from "./model-thinking";
|
|
54
54
|
/** Token budgets for each thinking level (token-based providers only) */
|
|
@@ -421,6 +421,8 @@ export interface UserMessage {
|
|
|
421
421
|
content: string | (TextContent | ImageContent)[];
|
|
422
422
|
/** True if the message was injected by the system (e.g., auto-continue). */
|
|
423
423
|
synthetic?: boolean;
|
|
424
|
+
/** True when injected mid-turn as a steer; consumed by the agent's pre-LLM transform to wrap it for emphasis. Never rendered. */
|
|
425
|
+
steering?: boolean;
|
|
424
426
|
/** Who initiated this message for billing/attribution semantics. */
|
|
425
427
|
attribution?: MessageAttribution;
|
|
426
428
|
/** Provider-specific opaque payload used to reconstruct transport-native history. */
|
|
@@ -650,6 +652,8 @@ export interface OpenAICompat {
|
|
|
650
652
|
requiresMistralToolIds?: boolean;
|
|
651
653
|
/** Format for reasoning/thinking parameter. "openai" uses reasoning_effort, "openrouter" uses reasoning: { effort }, "zai" uses thinking: { type: "enabled" | "disabled" } (also used by Moonshot Kimi), "qwen" uses top-level enable_thinking, and "qwen-chat-template" uses chat_template_kwargs.enable_thinking. Default: "openai". */
|
|
652
654
|
thinkingFormat?: "openai" | "openrouter" | "zai" | "qwen" | "qwen-chat-template";
|
|
655
|
+
/** Optional `thinking.keep` value for Z.ai/Moonshot-style thinking params. Set false to suppress auto-detected keep. Default: auto-detected. */
|
|
656
|
+
thinkingKeep?: "all" | false;
|
|
653
657
|
/** Which reasoning content field to emit on assistant messages. Default: auto-detected. */
|
|
654
658
|
reasoningContentField?: "reasoning_content" | "reasoning" | "reasoning_text";
|
|
655
659
|
/** Whether assistant tool-call messages must include reasoning content. Default: false. */
|
|
@@ -681,6 +685,8 @@ export interface OpenAICompat {
|
|
|
681
685
|
vercelGatewayRouting?: VercelGatewayRouting;
|
|
682
686
|
/** Extra fields to include in request body (e.g. gateway routing hints for OpenClaw-style proxies). */
|
|
683
687
|
extraBody?: Record<string, unknown>;
|
|
688
|
+
/** Whether chat-completions payloads should include provider-specific prompt-cache markers. */
|
|
689
|
+
cacheControlFormat?: "anthropic" | undefined;
|
|
684
690
|
/** Whether the provider supports the `strict` field in tool definitions. Default: auto-detected per provider/baseUrl (conservative for unknown providers). */
|
|
685
691
|
supportsStrictMode?: boolean;
|
|
686
692
|
/** Whether tool schemas must be sent either all strict or all non-strict. Undefined keeps the existing per-tool mixed behavior. */
|
|
@@ -7,7 +7,7 @@ export type OAuthCredentials = {
|
|
|
7
7
|
email?: string;
|
|
8
8
|
accountId?: string;
|
|
9
9
|
};
|
|
10
|
-
export type OAuthProvider = "alibaba-coding-plan" | "anthropic" | "cerebras" | "cloudflare-ai-gateway" | "cursor" | "deepseek" | "fireworks" | "firepass" | "github-copilot" | "google-gemini-cli" | "google-antigravity" | "gitlab-duo" | "huggingface" | "kimi-code" | "kilo" | "kagi" | "litellm" | "lm-studio" | "minimax-code" | "minimax-code-cn" | "moonshot" | "nvidia" | "nanogpt" | "ollama" | "ollama-cloud" | "openai-codex" | "openai-codex-device" | "opencode-go" | "openrouter" | "opencode-zen" | "parallel" | "perplexity" | "qianfan" | "qwen-portal" | "synthetic" | "tavily" | "together" | "venice" | "vercel-ai-gateway" | "wafer-pass" | "wafer-serverless" | "vllm" | "xai-oauth" | "xiaomi" | "zenmux" | "zai" | "zhipu-coding-plan";
|
|
10
|
+
export type OAuthProvider = "alibaba-coding-plan" | "anthropic" | "cerebras" | "cloudflare-ai-gateway" | "cursor" | "deepseek" | "fireworks" | "firepass" | "github-copilot" | "google-gemini-cli" | "google-antigravity" | "gitlab-duo" | "huggingface" | "kimi-code" | "kilo" | "kagi" | "litellm" | "lm-studio" | "minimax-code" | "minimax-code-cn" | "moonshot" | "nvidia" | "nanogpt" | "ollama" | "ollama-cloud" | "openai-codex" | "openai-codex-device" | "opencode-go" | "openrouter" | "opencode-zen" | "parallel" | "perplexity" | "qianfan" | "qwen-portal" | "synthetic" | "tavily" | "together" | "venice" | "vercel-ai-gateway" | "wafer-pass" | "wafer-serverless" | "vllm" | "xai-oauth" | "xiaomi" | "xiaomi-token-plan-sgp" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "zenmux" | "zai" | "zhipu-coding-plan";
|
|
11
11
|
export type OAuthProviderId = OAuthProvider | (string & {});
|
|
12
12
|
export type OAuthPrompt = {
|
|
13
13
|
message: string;
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
* Xiaomi MiMo provides OpenAI-compatible models via
|
|
5
5
|
* https://api.xiaomimimo.com/v1.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* 2. User copies their API key
|
|
10
|
-
* 3. User pastes the API key into the CLI
|
|
7
|
+
* Standard Xiaomi login opens the pay-as-you-go API key console. Token Plan
|
|
8
|
+
* login opens plan management so users copy the regional `tp-...` key.
|
|
11
9
|
*/
|
|
12
10
|
import type { OAuthController } from "./types";
|
|
11
|
+
/** Region codes accepted by the Xiaomi Token Plan login flow. */
|
|
12
|
+
export type XiaomiTokenPlanRegion = "sgp" | "ams" | "cn";
|
|
13
13
|
/**
|
|
14
14
|
* Login to Xiaomi MiMo.
|
|
15
15
|
*
|
|
@@ -17,3 +17,9 @@ import type { OAuthController } from "./types";
|
|
|
17
17
|
* Returns the API key directly (not OAuthCredentials - this isn't OAuth).
|
|
18
18
|
*/
|
|
19
19
|
export declare function loginXiaomi(options: OAuthController): Promise<string>;
|
|
20
|
+
/**
|
|
21
|
+
* Login to a regional Xiaomi Token Plan endpoint.
|
|
22
|
+
*
|
|
23
|
+
* Prompts for a token-plan API key and validates it against the selected region.
|
|
24
|
+
*/
|
|
25
|
+
export declare function loginXiaomiTokenPlan(options: OAuthController, region: XiaomiTokenPlanRegion): Promise<string>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-ai",
|
|
4
|
-
"version": "15.9.
|
|
4
|
+
"version": "15.9.1",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@bufbuild/protobuf": "^2.12.0",
|
|
42
|
-
"@oh-my-pi/pi-utils": "15.9.
|
|
42
|
+
"@oh-my-pi/pi-utils": "15.9.1",
|
|
43
43
|
"openai": "^6.39.0",
|
|
44
44
|
"partial-json": "^0.1.7",
|
|
45
45
|
"zod": "4.4.3"
|
package/src/auth-storage.ts
CHANGED
|
@@ -34,6 +34,7 @@ import { getOAuthApiKey, getOAuthProvider, refreshOAuthToken } from "./utils/oau
|
|
|
34
34
|
import { loginDeepSeek } from "./utils/oauth/deepseek";
|
|
35
35
|
import { loginOpenAICodexDevice } from "./utils/oauth/openai-codex";
|
|
36
36
|
import type { OAuthController, OAuthCredentials, OAuthProvider, OAuthProviderId } from "./utils/oauth/types";
|
|
37
|
+
import { loginXiaomi, loginXiaomiTokenPlan } from "./utils/oauth/xiaomi";
|
|
37
38
|
|
|
38
39
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
39
40
|
// Credential Types
|
|
@@ -1758,11 +1759,25 @@ export class AuthStorage {
|
|
|
1758
1759
|
return;
|
|
1759
1760
|
}
|
|
1760
1761
|
case "xiaomi": {
|
|
1761
|
-
const { loginXiaomi } = await import("./utils/oauth/xiaomi");
|
|
1762
1762
|
const apiKey = await loginXiaomi(ctrl);
|
|
1763
1763
|
await saveApiKeyCredential(apiKey);
|
|
1764
1764
|
return;
|
|
1765
1765
|
}
|
|
1766
|
+
case "xiaomi-token-plan-sgp": {
|
|
1767
|
+
const apiKey = await loginXiaomiTokenPlan(ctrl, "sgp");
|
|
1768
|
+
await saveApiKeyCredential(apiKey);
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1771
|
+
case "xiaomi-token-plan-ams": {
|
|
1772
|
+
const apiKey = await loginXiaomiTokenPlan(ctrl, "ams");
|
|
1773
|
+
await saveApiKeyCredential(apiKey);
|
|
1774
|
+
return;
|
|
1775
|
+
}
|
|
1776
|
+
case "xiaomi-token-plan-cn": {
|
|
1777
|
+
const apiKey = await loginXiaomiTokenPlan(ctrl, "cn");
|
|
1778
|
+
await saveApiKeyCredential(apiKey);
|
|
1779
|
+
return;
|
|
1780
|
+
}
|
|
1766
1781
|
case "zenmux": {
|
|
1767
1782
|
const { loginZenMux } = await import("./utils/oauth/zenmux");
|
|
1768
1783
|
const apiKey = await loginZenMux(ctrl);
|
package/src/models.json
CHANGED
|
@@ -2649,7 +2649,7 @@
|
|
|
2649
2649
|
},
|
|
2650
2650
|
"us.meta.llama4-scout-17b-instruct-v1:0": {
|
|
2651
2651
|
"id": "us.meta.llama4-scout-17b-instruct-v1:0",
|
|
2652
|
-
"name": "Llama 4 Scout 17B Instruct",
|
|
2652
|
+
"name": "Llama 4 Scout 17B Instruct (US)",
|
|
2653
2653
|
"api": "bedrock-converse-stream",
|
|
2654
2654
|
"provider": "amazon-bedrock",
|
|
2655
2655
|
"baseUrl": "https://bedrock-runtime.us-east-1.amazonaws.com",
|
|
@@ -5325,7 +5325,7 @@
|
|
|
5325
5325
|
"cacheWrite": 0
|
|
5326
5326
|
},
|
|
5327
5327
|
"contextWindow": 262144,
|
|
5328
|
-
"maxTokens":
|
|
5328
|
+
"maxTokens": 32768,
|
|
5329
5329
|
"thinking": {
|
|
5330
5330
|
"mode": "effort",
|
|
5331
5331
|
"minLevel": "minimal",
|
|
@@ -5469,7 +5469,7 @@
|
|
|
5469
5469
|
"cacheWrite": 0
|
|
5470
5470
|
},
|
|
5471
5471
|
"contextWindow": 262144,
|
|
5472
|
-
"maxTokens":
|
|
5472
|
+
"maxTokens": 32768,
|
|
5473
5473
|
"thinking": {
|
|
5474
5474
|
"mode": "effort",
|
|
5475
5475
|
"minLevel": "minimal",
|
|
@@ -5494,7 +5494,7 @@
|
|
|
5494
5494
|
"cacheWrite": 0
|
|
5495
5495
|
},
|
|
5496
5496
|
"contextWindow": 262144,
|
|
5497
|
-
"maxTokens":
|
|
5497
|
+
"maxTokens": 32768,
|
|
5498
5498
|
"thinking": {
|
|
5499
5499
|
"mode": "effort",
|
|
5500
5500
|
"minLevel": "minimal",
|
|
@@ -10438,7 +10438,7 @@
|
|
|
10438
10438
|
},
|
|
10439
10439
|
"anthropic/claude-opus-4.8": {
|
|
10440
10440
|
"id": "anthropic/claude-opus-4.8",
|
|
10441
|
-
"name": "Anthropic: Claude Opus 4.8
|
|
10441
|
+
"name": "Anthropic: Claude Opus 4.8",
|
|
10442
10442
|
"api": "openai-completions",
|
|
10443
10443
|
"provider": "kilo",
|
|
10444
10444
|
"baseUrl": "https://api.kilo.ai/api/gateway",
|
|
@@ -13187,7 +13187,7 @@
|
|
|
13187
13187
|
},
|
|
13188
13188
|
"minimax/minimax-m3": {
|
|
13189
13189
|
"id": "minimax/minimax-m3",
|
|
13190
|
-
"name": "MiniMax: MiniMax M3 (
|
|
13190
|
+
"name": "MiniMax: MiniMax M3 (50% off through 2026-06-07)",
|
|
13191
13191
|
"api": "openai-completions",
|
|
13192
13192
|
"provider": "kilo",
|
|
13193
13193
|
"baseUrl": "https://api.kilo.ai/api/gateway",
|
|
@@ -17142,6 +17142,25 @@
|
|
|
17142
17142
|
"contextWindow": 222222,
|
|
17143
17143
|
"maxTokens": 8888
|
|
17144
17144
|
},
|
|
17145
|
+
"qwen/qwen3.7-plus:free": {
|
|
17146
|
+
"id": "qwen/qwen3.7-plus:free",
|
|
17147
|
+
"name": "Qwen: Qwen3.7 Plus (free)",
|
|
17148
|
+
"api": "openai-completions",
|
|
17149
|
+
"provider": "kilo",
|
|
17150
|
+
"baseUrl": "https://api.kilo.ai/api/gateway",
|
|
17151
|
+
"reasoning": false,
|
|
17152
|
+
"input": [
|
|
17153
|
+
"text"
|
|
17154
|
+
],
|
|
17155
|
+
"cost": {
|
|
17156
|
+
"input": 0,
|
|
17157
|
+
"output": 0,
|
|
17158
|
+
"cacheRead": 0,
|
|
17159
|
+
"cacheWrite": 0
|
|
17160
|
+
},
|
|
17161
|
+
"contextWindow": 222222,
|
|
17162
|
+
"maxTokens": 8888
|
|
17163
|
+
},
|
|
17145
17164
|
"qwen/qwq-32b": {
|
|
17146
17165
|
"id": "qwen/qwq-32b",
|
|
17147
17166
|
"name": "Qwen: QwQ 32B",
|
|
@@ -28051,6 +28070,25 @@
|
|
|
28051
28070
|
"contextWindow": 222222,
|
|
28052
28071
|
"maxTokens": 8888
|
|
28053
28072
|
},
|
|
28073
|
+
"nex-agi/nex-n2-pro": {
|
|
28074
|
+
"id": "nex-agi/nex-n2-pro",
|
|
28075
|
+
"name": "nex-agi/nex-n2-pro",
|
|
28076
|
+
"api": "openai-completions",
|
|
28077
|
+
"provider": "nanogpt",
|
|
28078
|
+
"baseUrl": "https://nano-gpt.com/api/v1",
|
|
28079
|
+
"reasoning": false,
|
|
28080
|
+
"input": [
|
|
28081
|
+
"text"
|
|
28082
|
+
],
|
|
28083
|
+
"cost": {
|
|
28084
|
+
"input": 0,
|
|
28085
|
+
"output": 0,
|
|
28086
|
+
"cacheRead": 0,
|
|
28087
|
+
"cacheWrite": 0
|
|
28088
|
+
},
|
|
28089
|
+
"contextWindow": 222222,
|
|
28090
|
+
"maxTokens": 8888
|
|
28091
|
+
},
|
|
28054
28092
|
"nothingiisreal/L3.1-70B-Celeste-V0.1-BF16": {
|
|
28055
28093
|
"id": "nothingiisreal/L3.1-70B-Celeste-V0.1-BF16",
|
|
28056
28094
|
"name": "nothingiisreal/L3.1-70B-Celeste-V0.1-BF16",
|
|
@@ -31231,13 +31269,14 @@
|
|
|
31231
31269
|
},
|
|
31232
31270
|
"qwen3.7-plus": {
|
|
31233
31271
|
"id": "qwen3.7-plus",
|
|
31234
|
-
"name": "
|
|
31272
|
+
"name": "Qwen3.7 Plus",
|
|
31235
31273
|
"api": "openai-completions",
|
|
31236
31274
|
"provider": "nanogpt",
|
|
31237
31275
|
"baseUrl": "https://nano-gpt.com/api/v1",
|
|
31238
31276
|
"reasoning": true,
|
|
31239
31277
|
"input": [
|
|
31240
|
-
"text"
|
|
31278
|
+
"text",
|
|
31279
|
+
"image"
|
|
31241
31280
|
],
|
|
31242
31281
|
"cost": {
|
|
31243
31282
|
"input": 0,
|
|
@@ -31245,8 +31284,8 @@
|
|
|
31245
31284
|
"cacheRead": 0,
|
|
31246
31285
|
"cacheWrite": 0
|
|
31247
31286
|
},
|
|
31248
|
-
"contextWindow":
|
|
31249
|
-
"maxTokens":
|
|
31287
|
+
"contextWindow": 262144,
|
|
31288
|
+
"maxTokens": 65536,
|
|
31250
31289
|
"thinking": {
|
|
31251
31290
|
"mode": "effort",
|
|
31252
31291
|
"minLevel": "minimal",
|
|
@@ -32579,6 +32618,25 @@
|
|
|
32579
32618
|
"contextWindow": 222222,
|
|
32580
32619
|
"maxTokens": 8888
|
|
32581
32620
|
},
|
|
32621
|
+
"TEE/qwen3.6-27b": {
|
|
32622
|
+
"id": "TEE/qwen3.6-27b",
|
|
32623
|
+
"name": "TEE/qwen3.6-27b",
|
|
32624
|
+
"api": "openai-completions",
|
|
32625
|
+
"provider": "nanogpt",
|
|
32626
|
+
"baseUrl": "https://nano-gpt.com/api/v1",
|
|
32627
|
+
"reasoning": false,
|
|
32628
|
+
"input": [
|
|
32629
|
+
"text"
|
|
32630
|
+
],
|
|
32631
|
+
"cost": {
|
|
32632
|
+
"input": 0,
|
|
32633
|
+
"output": 0,
|
|
32634
|
+
"cacheRead": 0,
|
|
32635
|
+
"cacheWrite": 0
|
|
32636
|
+
},
|
|
32637
|
+
"contextWindow": 222222,
|
|
32638
|
+
"maxTokens": 8888
|
|
32639
|
+
},
|
|
32582
32640
|
"TEE/qwen3.6-35b-a3b-uncensored": {
|
|
32583
32641
|
"id": "TEE/qwen3.6-35b-a3b-uncensored",
|
|
32584
32642
|
"name": "TEE/qwen3.6-35b-a3b-uncensored",
|
|
@@ -38187,6 +38245,31 @@
|
|
|
38187
38245
|
"minLevel": "minimal",
|
|
38188
38246
|
"maxLevel": "xhigh"
|
|
38189
38247
|
}
|
|
38248
|
+
},
|
|
38249
|
+
"qwen3.7-plus": {
|
|
38250
|
+
"id": "qwen3.7-plus",
|
|
38251
|
+
"name": "Qwen3.7 Plus",
|
|
38252
|
+
"api": "anthropic-messages",
|
|
38253
|
+
"provider": "opencode-go",
|
|
38254
|
+
"baseUrl": "https://opencode.ai/zen/go",
|
|
38255
|
+
"reasoning": true,
|
|
38256
|
+
"input": [
|
|
38257
|
+
"text",
|
|
38258
|
+
"image"
|
|
38259
|
+
],
|
|
38260
|
+
"cost": {
|
|
38261
|
+
"input": 0.4,
|
|
38262
|
+
"output": 1.6,
|
|
38263
|
+
"cacheRead": 0.04,
|
|
38264
|
+
"cacheWrite": 0.5
|
|
38265
|
+
},
|
|
38266
|
+
"contextWindow": 262144,
|
|
38267
|
+
"maxTokens": 65536,
|
|
38268
|
+
"thinking": {
|
|
38269
|
+
"mode": "budget",
|
|
38270
|
+
"minLevel": "minimal",
|
|
38271
|
+
"maxLevel": "xhigh"
|
|
38272
|
+
}
|
|
38190
38273
|
}
|
|
38191
38274
|
},
|
|
38192
38275
|
"opencode-zen": {
|
|
@@ -45179,8 +45262,8 @@
|
|
|
45179
45262
|
"text"
|
|
45180
45263
|
],
|
|
45181
45264
|
"cost": {
|
|
45182
|
-
"input": 0.
|
|
45183
|
-
"output": 0.
|
|
45265
|
+
"input": 0.04815,
|
|
45266
|
+
"output": 0.19305,
|
|
45184
45267
|
"cacheRead": 0,
|
|
45185
45268
|
"cacheWrite": 0
|
|
45186
45269
|
},
|
|
@@ -49913,7 +49996,7 @@
|
|
|
49913
49996
|
"cacheRead": 0,
|
|
49914
49997
|
"cacheWrite": 0
|
|
49915
49998
|
},
|
|
49916
|
-
"contextWindow":
|
|
49999
|
+
"contextWindow": 1000000,
|
|
49917
50000
|
"maxTokens": 8888,
|
|
49918
50001
|
"compat": {
|
|
49919
50002
|
"supportsUsageInStreaming": false
|
|
@@ -312,6 +312,15 @@ export const PROVIDER_DESCRIPTORS: readonly ProviderDescriptor[] = [
|
|
|
312
312
|
config => xiaomiModelManagerOptions(config),
|
|
313
313
|
catalog("Xiaomi", ["XIAOMI_API_KEY"]),
|
|
314
314
|
),
|
|
315
|
+
descriptor("xiaomi-token-plan-sgp", "mimo-v2.5", config =>
|
|
316
|
+
xiaomiModelManagerOptions({ ...config, providerId: "xiaomi-token-plan-sgp", tokenPlanRegion: "sgp" }),
|
|
317
|
+
),
|
|
318
|
+
descriptor("xiaomi-token-plan-ams", "mimo-v2.5", config =>
|
|
319
|
+
xiaomiModelManagerOptions({ ...config, providerId: "xiaomi-token-plan-ams", tokenPlanRegion: "ams" }),
|
|
320
|
+
),
|
|
321
|
+
descriptor("xiaomi-token-plan-cn", "mimo-v2.5", config =>
|
|
322
|
+
xiaomiModelManagerOptions({ ...config, providerId: "xiaomi-token-plan-cn", tokenPlanRegion: "cn" }),
|
|
323
|
+
),
|
|
315
324
|
catalogDescriptor(
|
|
316
325
|
"zenmux",
|
|
317
326
|
"anthropic/claude-opus-4.6",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ModelManagerOptions } from "../model-manager";
|
|
2
2
|
import { Effort } from "../model-thinking";
|
|
3
3
|
import { getBundledModels } from "../models";
|
|
4
|
-
import type { Api, Model, ThinkingConfig } from "../types";
|
|
4
|
+
import type { Api, Model, Provider, ThinkingConfig } from "../types";
|
|
5
5
|
import { isAnthropicOAuthToken, isRecord, toBoolean, toNumber, toPositiveNumber } from "../utils";
|
|
6
6
|
import {
|
|
7
7
|
fetchOpenAICompatibleModels,
|
|
@@ -15,7 +15,7 @@ import { createBundledReferenceMap, createReferenceResolver } from "./bundled-re
|
|
|
15
15
|
const MODELS_DEV_URL = "https://models.dev/api.json";
|
|
16
16
|
const ANTHROPIC_BASE_URL = "https://api.anthropic.com/v1";
|
|
17
17
|
const ANTHROPIC_OAUTH_BETA =
|
|
18
|
-
"claude-code-20250219,oauth-2025-04-20,
|
|
18
|
+
"claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,redact-thinking-2026-02-12,context-management-2025-06-27,prompt-caching-scope-2026-01-05,mid-conversation-system-2026-04-07,advanced-tool-use-2025-11-20,effort-2025-11-24,extended-cache-ttl-2025-04-11";
|
|
19
19
|
|
|
20
20
|
export interface ModelsDevModel {
|
|
21
21
|
id?: string;
|
|
@@ -925,6 +925,36 @@ const ZHIPU_VISION_PATTERN = /^glm-[45](?:\.\d+)?v(?:-|$)/;
|
|
|
925
925
|
// 7.5 Fireworks
|
|
926
926
|
// ---------------------------------------------------------------------------
|
|
927
927
|
|
|
928
|
+
/**
|
|
929
|
+
* Fireworks-published cap for the Kimi K2 family. Fireworks' `/v1/models`
|
|
930
|
+
* envelope generically reports `max_completion_tokens: 65536` for every Kimi
|
|
931
|
+
* deployment, but Kimi K2 (instruct / thinking / turbo) on Fireworks is
|
|
932
|
+
* documented to ship long reasoning traces that should be bounded — capping
|
|
933
|
+
* at 32,768 prevents handing callers a budget the router cannot honor.
|
|
934
|
+
* See https://github.com/can1357/oh-my-pi/issues/1849.
|
|
935
|
+
*/
|
|
936
|
+
export const FIREWORKS_KIMI_MAX_TOKENS = 32_768;
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* Returns true for any Kimi K2.x public model id served by Fireworks-backed
|
|
940
|
+
* providers (`fireworks` direct, `firepass` router). Matches both the public
|
|
941
|
+
* catalog id (`kimi-k2.5`, `kimi-k2.6`, `kimi-k2.6-turbo`) and the canonical
|
|
942
|
+
* Fireworks wire id (`accounts/fireworks/{models,routers}/kimi-k2…`).
|
|
943
|
+
*/
|
|
944
|
+
export function isFireworksKimiK2ModelId(modelId: string): boolean {
|
|
945
|
+
const trimmed = modelId.toLowerCase();
|
|
946
|
+
if (trimmed.startsWith("kimi-k2")) return true;
|
|
947
|
+
return /\/kimi-k2(?:p\d+)?(?:[._-]|$)/.test(trimmed);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
/**
|
|
951
|
+
* Clamp the Kimi K2 family's `maxTokens` to {@link FIREWORKS_KIMI_MAX_TOKENS}
|
|
952
|
+
* on Fireworks-backed providers, leaving every other model untouched.
|
|
953
|
+
*/
|
|
954
|
+
export function clampFireworksKimiMaxTokens(modelId: string, candidate: number): number {
|
|
955
|
+
return isFireworksKimiK2ModelId(modelId) ? Math.min(candidate, FIREWORKS_KIMI_MAX_TOKENS) : candidate;
|
|
956
|
+
}
|
|
957
|
+
|
|
928
958
|
export interface FireworksModelManagerConfig {
|
|
929
959
|
apiKey?: string;
|
|
930
960
|
baseUrl?: string;
|
|
@@ -1004,7 +1034,10 @@ export function fireworksModelManagerOptions(
|
|
|
1004
1034
|
name: toFireworksModelName(entry, model.name),
|
|
1005
1035
|
input: toBoolean(entry.supports_image_input) === true ? ["text", "image"] : ["text"],
|
|
1006
1036
|
contextWindow: toPositiveNumber(entry.context_length, model.contextWindow),
|
|
1007
|
-
maxTokens:
|
|
1037
|
+
maxTokens: clampFireworksKimiMaxTokens(
|
|
1038
|
+
publicModelId,
|
|
1039
|
+
toPositiveNumber(entry.max_completion_tokens, model.maxTokens),
|
|
1040
|
+
),
|
|
1008
1041
|
};
|
|
1009
1042
|
},
|
|
1010
1043
|
});
|
|
@@ -1905,33 +1938,49 @@ export function cloudflareAiGatewayModelManagerOptions(
|
|
|
1905
1938
|
// 20. Xiaomi
|
|
1906
1939
|
// ---------------------------------------------------------------------------
|
|
1907
1940
|
|
|
1941
|
+
/** Region codes for Xiaomi Token Plan clusters exposed as separate login providers. */
|
|
1942
|
+
export type XiaomiTokenPlanRegion = "sgp" | "ams" | "cn";
|
|
1943
|
+
|
|
1944
|
+
/** Configures Xiaomi standard or regional Token Plan OpenAI-compatible model discovery. */
|
|
1908
1945
|
export interface XiaomiModelManagerConfig {
|
|
1909
1946
|
apiKey?: string;
|
|
1910
1947
|
baseUrl?: string;
|
|
1948
|
+
providerId?: Provider;
|
|
1949
|
+
tokenPlanRegion?: XiaomiTokenPlanRegion;
|
|
1911
1950
|
}
|
|
1912
1951
|
|
|
1952
|
+
const XIAOMI_TOKEN_PLAN_BASE_URLS: Record<XiaomiTokenPlanRegion, string> = {
|
|
1953
|
+
sgp: "https://token-plan-sgp.xiaomimimo.com/v1",
|
|
1954
|
+
ams: "https://token-plan-ams.xiaomimimo.com/v1",
|
|
1955
|
+
cn: "https://token-plan-cn.xiaomimimo.com/v1",
|
|
1956
|
+
};
|
|
1957
|
+
|
|
1958
|
+
const XIAOMI_TOKEN_PLAN_FALLBACK_BASE_URLS = [
|
|
1959
|
+
XIAOMI_TOKEN_PLAN_BASE_URLS.sgp,
|
|
1960
|
+
XIAOMI_TOKEN_PLAN_BASE_URLS.ams,
|
|
1961
|
+
XIAOMI_TOKEN_PLAN_BASE_URLS.cn,
|
|
1962
|
+
];
|
|
1963
|
+
|
|
1964
|
+
/** Builds a Xiaomi model manager, preserving Token Plan region provider ids during discovery. */
|
|
1913
1965
|
export function xiaomiModelManagerOptions(
|
|
1914
1966
|
config?: XiaomiModelManagerConfig,
|
|
1915
1967
|
): ModelManagerOptions<"openai-completions"> {
|
|
1916
1968
|
const apiKey = config?.apiKey;
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
] as const;
|
|
1925
|
-
const STANDARD_BASE_URL = "https://api.xiaomimimo.com/v1";
|
|
1926
|
-
const isTokenPlanKey = apiKey?.startsWith("tp-");
|
|
1969
|
+
const providerId = config?.providerId ?? "xiaomi";
|
|
1970
|
+
const tokenPlanBaseUrls = config?.tokenPlanRegion
|
|
1971
|
+
? [XIAOMI_TOKEN_PLAN_BASE_URLS[config.tokenPlanRegion]]
|
|
1972
|
+
: XIAOMI_TOKEN_PLAN_FALLBACK_BASE_URLS;
|
|
1973
|
+
const XIAOMI_STANDARD_BASE_URL = "https://api.xiaomimimo.com/v1";
|
|
1974
|
+
const isTokenPlanProvider = config?.tokenPlanRegion !== undefined || providerId.startsWith("xiaomi-token-plan-");
|
|
1975
|
+
const isTokenPlanKey = isTokenPlanProvider || apiKey?.startsWith("tp-");
|
|
1927
1976
|
// Token-plan keys always use a TP cluster; config?.baseUrl (from catalog)
|
|
1928
1977
|
// would incorrectly pin to the standard endpoint (api.xiaomimimo.com).
|
|
1929
|
-
const baseUrl = isTokenPlanKey ?
|
|
1978
|
+
const baseUrl = isTokenPlanKey ? tokenPlanBaseUrls[0] : (config?.baseUrl ?? XIAOMI_STANDARD_BASE_URL);
|
|
1930
1979
|
const references = createBundledReferenceMap<"openai-completions">("xiaomi");
|
|
1931
1980
|
const fetchModels = (url: string) =>
|
|
1932
1981
|
fetchOpenAICompatibleModels({
|
|
1933
1982
|
api: "openai-completions",
|
|
1934
|
-
provider:
|
|
1983
|
+
provider: providerId,
|
|
1935
1984
|
baseUrl: url,
|
|
1936
1985
|
apiKey,
|
|
1937
1986
|
filterModel: (_entry, model) => !model.id.includes("-tts"),
|
|
@@ -1940,18 +1989,21 @@ export function xiaomiModelManagerOptions(
|
|
|
1940
1989
|
const model = mapWithBundledReference(entry, defaults, reference);
|
|
1941
1990
|
return {
|
|
1942
1991
|
...model,
|
|
1992
|
+
api: "openai-completions",
|
|
1993
|
+
provider: providerId,
|
|
1994
|
+
baseUrl: defaults.baseUrl,
|
|
1943
1995
|
name: toModelName(entry.display_name, model.name),
|
|
1944
1996
|
};
|
|
1945
1997
|
},
|
|
1946
1998
|
});
|
|
1947
1999
|
return {
|
|
1948
|
-
providerId
|
|
2000
|
+
providerId,
|
|
1949
2001
|
...(apiKey && {
|
|
1950
2002
|
fetchDynamicModels: async () => {
|
|
1951
2003
|
if (!isTokenPlanKey) {
|
|
1952
2004
|
return fetchModels(baseUrl);
|
|
1953
2005
|
}
|
|
1954
|
-
for (const url of
|
|
2006
|
+
for (const url of tokenPlanBaseUrls) {
|
|
1955
2007
|
const result = await fetchModels(url);
|
|
1956
2008
|
if (result) return result;
|
|
1957
2009
|
}
|
|
@@ -132,7 +132,6 @@ const claudeCodeUtilityBetaDefaults = [
|
|
|
132
132
|
const claudeCodeAgentBetaDefaults = [
|
|
133
133
|
"claude-code-20250219",
|
|
134
134
|
"oauth-2025-04-20",
|
|
135
|
-
"context-1m-2025-08-07",
|
|
136
135
|
"interleaved-thinking-2025-05-14",
|
|
137
136
|
"context-management-2025-06-27",
|
|
138
137
|
"prompt-caching-scope-2026-01-05",
|
|
@@ -4,11 +4,21 @@ type OpenAIReasoningEffort = "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
|
4
4
|
type ResolvedToolStrictMode = NonNullable<OpenAICompat["toolStrictMode"]> | "mixed";
|
|
5
5
|
|
|
6
6
|
export type ResolvedOpenAICompat = Required<
|
|
7
|
-
Omit<
|
|
7
|
+
Omit<
|
|
8
|
+
OpenAICompat,
|
|
9
|
+
| "openRouterRouting"
|
|
10
|
+
| "vercelGatewayRouting"
|
|
11
|
+
| "extraBody"
|
|
12
|
+
| "toolStrictMode"
|
|
13
|
+
| "cacheControlFormat"
|
|
14
|
+
| "thinkingKeep"
|
|
15
|
+
>
|
|
8
16
|
> & {
|
|
9
17
|
openRouterRouting?: OpenAICompat["openRouterRouting"];
|
|
10
18
|
vercelGatewayRouting?: OpenAICompat["vercelGatewayRouting"];
|
|
11
19
|
extraBody?: OpenAICompat["extraBody"];
|
|
20
|
+
cacheControlFormat?: OpenAICompat["cacheControlFormat"];
|
|
21
|
+
thinkingKeep?: OpenAICompat["thinkingKeep"];
|
|
12
22
|
toolStrictMode: ResolvedToolStrictMode;
|
|
13
23
|
};
|
|
14
24
|
|
|
@@ -54,12 +64,10 @@ export function detectOpenAICompat(model: Model<"openai-completions">, resolvedB
|
|
|
54
64
|
const isZhipu = provider === "zhipu-coding-plan" || baseUrl.includes("open.bigmodel.cn");
|
|
55
65
|
const isKilo = provider === "kilo" || baseUrl.includes("api.kilo.ai");
|
|
56
66
|
const isKimiModel = model.id.includes("moonshotai/kimi") || /(^|\/)kimi[-.]/i.test(model.id);
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
baseUrl.includes("api.moonshot.ai") ||
|
|
62
|
-
baseUrl.includes("api.kimi.com"));
|
|
67
|
+
const isMoonshotNativeHost =
|
|
68
|
+
provider === "moonshot" || provider === "kimi-code" || /api\.moonshot\.ai|api\.kimi\.com/i.test(baseUrl);
|
|
69
|
+
const isMoonshotKimi = isKimiModel && isMoonshotNativeHost;
|
|
70
|
+
const usesMoonshotKimiPreservedThinking = isMoonshotKimi && /(^|\/)kimi-k2\.6(?:[-:]|$)/i.test(model.id);
|
|
63
71
|
const isAnthropicModel =
|
|
64
72
|
provider === "anthropic" ||
|
|
65
73
|
baseUrl.includes("api.anthropic.com") ||
|
|
@@ -75,6 +83,10 @@ export function detectOpenAICompat(model: Model<"openai-completions">, resolvedB
|
|
|
75
83
|
// applies when thinking mode is actually engaged.
|
|
76
84
|
const lowerId = model.id.toLowerCase();
|
|
77
85
|
const lowerName = (model.name ?? "").toLowerCase();
|
|
86
|
+
const isXiaomiHost =
|
|
87
|
+
provider === "xiaomi" || provider.startsWith("xiaomi-token-plan-") || baseUrl.includes("xiaomimimo.com");
|
|
88
|
+
const isMimoModel = lowerId.includes("mimo") || lowerName.includes("mimo");
|
|
89
|
+
const isXiaomiMimo = isXiaomiHost && isMimoModel;
|
|
78
90
|
// OpenCode Zen's `big-pickle` is a DeepSeek reasoning alias; the upstream
|
|
79
91
|
// 400s come from DeepSeek and require exact reasoning_content replay.
|
|
80
92
|
const isOpenCodeDeepseekAlias =
|
|
@@ -101,6 +113,7 @@ export function detectOpenAICompat(model: Model<"openai-completions">, resolvedB
|
|
|
101
113
|
isZhipu ||
|
|
102
114
|
isKilo ||
|
|
103
115
|
isQwen ||
|
|
116
|
+
isXiaomiHost ||
|
|
104
117
|
provider === "opencode-zen" ||
|
|
105
118
|
provider === "opencode-go" ||
|
|
106
119
|
baseUrl.includes("opencode.ai");
|
|
@@ -197,7 +210,7 @@ export function detectOpenAICompat(model: Model<"openai-completions">, resolvedB
|
|
|
197
210
|
// OpenAI's reasoning-API surface.
|
|
198
211
|
supportsDeveloperRole: isOpenAIHost || isAzureHost,
|
|
199
212
|
supportsMultipleSystemMessages: supportsMultipleSystemMessagesDefault,
|
|
200
|
-
supportsReasoningEffort: !isGrok && !isZai && !isZhipu,
|
|
213
|
+
supportsReasoningEffort: !isGrok && !isZai && !isZhipu && !isXiaomiMimo,
|
|
201
214
|
reasoningEffortMap,
|
|
202
215
|
supportsUsageInStreaming: !isCerebras,
|
|
203
216
|
disableReasoningOnForcedToolChoice: isKimiModel || isAnthropicModel,
|
|
@@ -214,18 +227,21 @@ export function detectOpenAICompat(model: Model<"openai-completions">, resolvedB
|
|
|
214
227
|
// etc. — drives reasoning via OpenAI-style `reasoning_effort`
|
|
215
228
|
// (low|medium|high|xhigh|max|none), so those stay on the "openai" path.
|
|
216
229
|
thinkingFormat:
|
|
217
|
-
isZai || isZhipu || isMoonshotKimi
|
|
230
|
+
isZai || isZhipu || isMoonshotKimi || isXiaomiMimo
|
|
218
231
|
? "zai"
|
|
219
232
|
: provider === "openrouter" || baseUrl.includes("openrouter.ai")
|
|
220
233
|
? "openrouter"
|
|
221
234
|
: isAlibaba || isQwen
|
|
222
235
|
? "qwen"
|
|
223
236
|
: "openai",
|
|
237
|
+
thinkingKeep: usesMoonshotKimiPreservedThinking ? "all" : undefined,
|
|
224
238
|
reasoningContentField: "reasoning_content",
|
|
225
239
|
// Backends that 400 follow-up requests when prior assistant tool-call turns lack `reasoning_content`:
|
|
226
240
|
// - Kimi: documented invariant on its native API.
|
|
227
241
|
// - DeepSeek-family reasoning models, including aliased OpenCode Zen models
|
|
228
242
|
// like `big-pickle`, validate exact thinking-mode replay.
|
|
243
|
+
// - Xiaomi MiMo models require exact `reasoning_content` replay on
|
|
244
|
+
// thinking-mode tool-call continuations across standard and Token Plan hosts.
|
|
229
245
|
// - Any reasoning-capable model reached through OpenRouter can enforce this
|
|
230
246
|
// server-side whenever the request is in thinking mode. We can't translate
|
|
231
247
|
// Anthropic's redacted/encrypted reasoning into provider-native plaintext,
|
|
@@ -235,11 +251,13 @@ export function detectOpenAICompat(model: Model<"openai-completions">, resolvedB
|
|
|
235
251
|
requiresReasoningContentForToolCalls:
|
|
236
252
|
(isKimiModel && !isOpenCodeProvider) ||
|
|
237
253
|
(isDeepseekFamily && Boolean(model.reasoning)) ||
|
|
254
|
+
isXiaomiMimo ||
|
|
238
255
|
((provider === "openrouter" || baseUrl.includes("openrouter.ai")) && Boolean(model.reasoning)),
|
|
239
|
-
// DeepSeek V4
|
|
256
|
+
// DeepSeek V4 and Xiaomi MiMo reject synthetic reasoning_content placeholders (".") on tool-call turns.
|
|
240
257
|
// Kimi and OpenRouter accept them when actual reasoning is unavailable.
|
|
241
|
-
allowsSyntheticReasoningContentForToolCalls: !isDeepseekFamily || !model.reasoning,
|
|
258
|
+
allowsSyntheticReasoningContentForToolCalls: (!isDeepseekFamily || !model.reasoning) && !isXiaomiMimo,
|
|
242
259
|
requiresAssistantContentForToolCalls: isKimiModel || isDirectDeepseekReasoning,
|
|
260
|
+
cacheControlFormat: isOpenRouter && model.id.startsWith("anthropic/") ? "anthropic" : undefined,
|
|
243
261
|
openRouterRouting: undefined,
|
|
244
262
|
vercelGatewayRouting: undefined,
|
|
245
263
|
supportsStrictMode: detectStrictModeSupport(provider, baseUrl),
|
|
@@ -280,6 +298,7 @@ export function resolveOpenAICompat(
|
|
|
280
298
|
requiresThinkingAsText: model.compat.requiresThinkingAsText ?? detected.requiresThinkingAsText,
|
|
281
299
|
requiresMistralToolIds: model.compat.requiresMistralToolIds ?? detected.requiresMistralToolIds,
|
|
282
300
|
thinkingFormat: model.compat.thinkingFormat ?? detected.thinkingFormat,
|
|
301
|
+
thinkingKeep: model.compat.thinkingKeep ?? detected.thinkingKeep,
|
|
283
302
|
reasoningContentField: model.compat.reasoningContentField ?? detected.reasoningContentField,
|
|
284
303
|
requiresReasoningContentForToolCalls:
|
|
285
304
|
model.compat.requiresReasoningContentForToolCalls ?? detected.requiresReasoningContentForToolCalls,
|
|
@@ -288,6 +307,7 @@ export function resolveOpenAICompat(
|
|
|
288
307
|
detected.allowsSyntheticReasoningContentForToolCalls,
|
|
289
308
|
requiresAssistantContentForToolCalls:
|
|
290
309
|
model.compat.requiresAssistantContentForToolCalls ?? detected.requiresAssistantContentForToolCalls,
|
|
310
|
+
cacheControlFormat: model.compat.cacheControlFormat ?? detected.cacheControlFormat,
|
|
291
311
|
disableReasoningOnForcedToolChoice:
|
|
292
312
|
model.compat.disableReasoningOnForcedToolChoice ?? detected.disableReasoningOnForcedToolChoice,
|
|
293
313
|
disableReasoningOnToolChoice: model.compat.disableReasoningOnToolChoice ?? detected.disableReasoningOnToolChoice,
|
|
@@ -72,7 +72,11 @@ import {
|
|
|
72
72
|
import { detectOpenAICompat, type ResolvedOpenAICompat, resolveOpenAICompat } from "./openai-completions-compat";
|
|
73
73
|
import { createInitialResponsesAssistantMessage } from "./openai-responses-shared";
|
|
74
74
|
import { transformMessages } from "./transform-messages";
|
|
75
|
-
import {
|
|
75
|
+
import {
|
|
76
|
+
isDashscopeCompatibleModeTextOnlyQwen,
|
|
77
|
+
joinTextWithImagePlaceholder,
|
|
78
|
+
NON_VISION_IMAGE_PLACEHOLDER,
|
|
79
|
+
} from "./vision-guard";
|
|
76
80
|
|
|
77
81
|
/**
|
|
78
82
|
* Normalize tool call ID for Mistral.
|
|
@@ -255,7 +259,7 @@ type OpenAICompletionsParams = OpenAI.Chat.Completions.ChatCompletionCreateParam
|
|
|
255
259
|
top_k?: number;
|
|
256
260
|
min_p?: number;
|
|
257
261
|
repetition_penalty?: number;
|
|
258
|
-
thinking?: { type: "enabled" | "disabled" };
|
|
262
|
+
thinking?: { type: "enabled" | "disabled"; keep?: "all" };
|
|
259
263
|
enable_thinking?: boolean;
|
|
260
264
|
chat_template_kwargs?: { enable_thinking: boolean };
|
|
261
265
|
reasoning?: { effort?: string } | { enabled: false };
|
|
@@ -1147,7 +1151,7 @@ function buildParams(
|
|
|
1147
1151
|
}
|
|
1148
1152
|
const isKimiModelId = model.id.includes("moonshotai/kimi") || /(^|\/)kimi[-.]/i.test(model.id);
|
|
1149
1153
|
const messages = convertMessages(model, context, compat);
|
|
1150
|
-
|
|
1154
|
+
maybeAddAnthropicCacheControl(compat, messages);
|
|
1151
1155
|
const supportsReasoningParams = model.provider !== "github-copilot";
|
|
1152
1156
|
|
|
1153
1157
|
// Kimi (including via OpenRouter and Fireworks router-form IDs such as
|
|
@@ -1252,6 +1256,9 @@ function buildParams(
|
|
|
1252
1256
|
// Must explicitly disable since z.ai defaults to thinking enabled.
|
|
1253
1257
|
const enabled = options?.reasoning && !options?.disableReasoning;
|
|
1254
1258
|
params.thinking = { type: enabled ? "enabled" : "disabled" };
|
|
1259
|
+
if (enabled && compat.thinkingKeep) {
|
|
1260
|
+
params.thinking.keep = compat.thinkingKeep;
|
|
1261
|
+
}
|
|
1255
1262
|
} else if (supportsReasoningParams && compat.thinkingFormat === "qwen" && model.reasoning) {
|
|
1256
1263
|
// Qwen uses top-level enable_thinking: boolean
|
|
1257
1264
|
params.enable_thinking = !!options?.reasoning && !options?.disableReasoning;
|
|
@@ -1430,12 +1437,8 @@ function mapReasoningEffort(
|
|
|
1430
1437
|
return reasoningEffortMap[effort] ?? effort;
|
|
1431
1438
|
}
|
|
1432
1439
|
|
|
1433
|
-
function
|
|
1434
|
-
|
|
1435
|
-
messages: ChatCompletionMessageParam[],
|
|
1436
|
-
): void {
|
|
1437
|
-
if (model.provider !== "openrouter" || !model.id.startsWith("anthropic/")) return;
|
|
1438
|
-
|
|
1440
|
+
function maybeAddAnthropicCacheControl(compat: ResolvedOpenAICompat, messages: ChatCompletionMessageParam[]): void {
|
|
1441
|
+
if (compat.cacheControlFormat !== "anthropic") return;
|
|
1439
1442
|
// Anthropic-style caching requires cache_control on a text part. Add a breakpoint
|
|
1440
1443
|
// on the last user/assistant message (walking backwards until we find text content).
|
|
1441
1444
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -1567,7 +1570,7 @@ export function convertMessages(
|
|
|
1567
1570
|
content: text,
|
|
1568
1571
|
});
|
|
1569
1572
|
} else {
|
|
1570
|
-
const supportsImages = model.input.includes("image");
|
|
1573
|
+
const supportsImages = model.input.includes("image") && !isDashscopeCompatibleModeTextOnlyQwen(model);
|
|
1571
1574
|
const content: ChatCompletionContentPart[] = [];
|
|
1572
1575
|
let omittedImages = false;
|
|
1573
1576
|
for (const item of msg.content) {
|
|
@@ -1810,7 +1813,7 @@ export function convertMessages(
|
|
|
1810
1813
|
.filter(c => c.type === "text")
|
|
1811
1814
|
.map(c => (c as TextContent).text)
|
|
1812
1815
|
.join("\n");
|
|
1813
|
-
const supportsImages = model.input.includes("image");
|
|
1816
|
+
const supportsImages = model.input.includes("image") && !isDashscopeCompatibleModeTextOnlyQwen(model);
|
|
1814
1817
|
const hasImages = toolMsg.content.some(c => c.type === "image");
|
|
1815
1818
|
const omittedImages = hasImages && !supportsImages;
|
|
1816
1819
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ImageContent, TextContent } from "../types";
|
|
1
|
+
import type { ImageContent, Model, TextContent } from "../types";
|
|
2
2
|
|
|
3
3
|
export const NON_VISION_IMAGE_PLACEHOLDER = "[image omitted: model does not support vision]";
|
|
4
4
|
|
|
@@ -29,3 +29,24 @@ export function joinTextWithImagePlaceholder(text: string, omittedImages: boolea
|
|
|
29
29
|
}
|
|
30
30
|
return parts.join("\n");
|
|
31
31
|
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Detect known text-only Qwen models served via Alibaba DashScope's consumer
|
|
35
|
+
* `compatible-mode` endpoint that the upstream chat-completions API rejects
|
|
36
|
+
* multimodal content arrays for. The compatible-mode endpoint also serves
|
|
37
|
+
* multimodal Qwen SKUs without `vl` in the id (e.g. `qwen3.7-plus`), so this
|
|
38
|
+
* guard only covers families verified to be text-only for issue #1859:
|
|
39
|
+
* `qwen*-max` and `qwen*-coder*`.
|
|
40
|
+
*
|
|
41
|
+
* Used as a defensive override in `convertMessages` so a misconfigured custom
|
|
42
|
+
* provider (issue #1859) can't drive the request into an unrecoverable 400.
|
|
43
|
+
*/
|
|
44
|
+
export function isDashscopeCompatibleModeTextOnlyQwen(model: Model<"openai-completions">): boolean {
|
|
45
|
+
const baseUrl = model.baseUrl.toLowerCase();
|
|
46
|
+
if (!baseUrl.includes("dashscope") || !baseUrl.includes("aliyuncs.com") || !baseUrl.includes("/compatible-mode")) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
const id = model.id.toLowerCase();
|
|
50
|
+
if (!id.includes("qwen")) return false;
|
|
51
|
+
return /\bqwen(?:[\d.]+)?-max\b/.test(id) || /\bqwen(?:[\d.]+)?-coder\b/.test(id);
|
|
52
|
+
}
|
package/src/stream.ts
CHANGED
|
@@ -266,6 +266,9 @@ const serviceProviderMap: Record<string, KeyResolver> = {
|
|
|
266
266
|
venice: "VENICE_API_KEY",
|
|
267
267
|
vllm: "VLLM_API_KEY",
|
|
268
268
|
xiaomi: "XIAOMI_API_KEY",
|
|
269
|
+
"xiaomi-token-plan-sgp": "XIAOMI_TOKEN_PLAN_SGP_API_KEY",
|
|
270
|
+
"xiaomi-token-plan-ams": "XIAOMI_TOKEN_PLAN_AMS_API_KEY",
|
|
271
|
+
"xiaomi-token-plan-cn": "XIAOMI_TOKEN_PLAN_CN_API_KEY",
|
|
269
272
|
};
|
|
270
273
|
|
|
271
274
|
/**
|
package/src/types.ts
CHANGED
|
@@ -142,6 +142,9 @@ export type KnownProvider =
|
|
|
142
142
|
| "venice"
|
|
143
143
|
| "vllm"
|
|
144
144
|
| "xiaomi"
|
|
145
|
+
| "xiaomi-token-plan-sgp"
|
|
146
|
+
| "xiaomi-token-plan-ams"
|
|
147
|
+
| "xiaomi-token-plan-cn"
|
|
145
148
|
| "wafer-pass"
|
|
146
149
|
| "wafer-serverless"
|
|
147
150
|
| "zenmux"
|
|
@@ -576,6 +579,8 @@ export interface UserMessage {
|
|
|
576
579
|
content: string | (TextContent | ImageContent)[];
|
|
577
580
|
/** True if the message was injected by the system (e.g., auto-continue). */
|
|
578
581
|
synthetic?: boolean;
|
|
582
|
+
/** True when injected mid-turn as a steer; consumed by the agent's pre-LLM transform to wrap it for emphasis. Never rendered. */
|
|
583
|
+
steering?: boolean;
|
|
579
584
|
/** Who initiated this message for billing/attribution semantics. */
|
|
580
585
|
attribution?: MessageAttribution;
|
|
581
586
|
/** Provider-specific opaque payload used to reconstruct transport-native history. */
|
|
@@ -784,6 +789,8 @@ export interface OpenAICompat {
|
|
|
784
789
|
requiresMistralToolIds?: boolean;
|
|
785
790
|
/** Format for reasoning/thinking parameter. "openai" uses reasoning_effort, "openrouter" uses reasoning: { effort }, "zai" uses thinking: { type: "enabled" | "disabled" } (also used by Moonshot Kimi), "qwen" uses top-level enable_thinking, and "qwen-chat-template" uses chat_template_kwargs.enable_thinking. Default: "openai". */
|
|
786
791
|
thinkingFormat?: "openai" | "openrouter" | "zai" | "qwen" | "qwen-chat-template";
|
|
792
|
+
/** Optional `thinking.keep` value for Z.ai/Moonshot-style thinking params. Set false to suppress auto-detected keep. Default: auto-detected. */
|
|
793
|
+
thinkingKeep?: "all" | false;
|
|
787
794
|
/** Which reasoning content field to emit on assistant messages. Default: auto-detected. */
|
|
788
795
|
reasoningContentField?: "reasoning_content" | "reasoning" | "reasoning_text";
|
|
789
796
|
/** Whether assistant tool-call messages must include reasoning content. Default: false. */
|
|
@@ -815,6 +822,8 @@ export interface OpenAICompat {
|
|
|
815
822
|
vercelGatewayRouting?: VercelGatewayRouting;
|
|
816
823
|
/** Extra fields to include in request body (e.g. gateway routing hints for OpenClaw-style proxies). */
|
|
817
824
|
extraBody?: Record<string, unknown>;
|
|
825
|
+
/** Whether chat-completions payloads should include provider-specific prompt-cache markers. */
|
|
826
|
+
cacheControlFormat?: "anthropic" | undefined;
|
|
818
827
|
/** Whether the provider supports the `strict` field in tool definitions. Default: auto-detected per provider/baseUrl (conservative for unknown providers). */
|
|
819
828
|
supportsStrictMode?: boolean;
|
|
820
829
|
/** Whether tool schemas must be sent either all strict or all non-strict. Undefined keeps the existing per-tool mixed behavior. */
|
package/src/usage/claude.ts
CHANGED
|
@@ -22,7 +22,7 @@ const CLAUDE_HEADERS = {
|
|
|
22
22
|
accept: "application/json, text/plain, */*",
|
|
23
23
|
"accept-encoding": "gzip, compress, deflate, br",
|
|
24
24
|
"anthropic-beta":
|
|
25
|
-
"claude-code-20250219,oauth-2025-04-20,
|
|
25
|
+
"claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,redact-thinking-2026-02-12,context-management-2025-06-27,prompt-caching-scope-2026-01-05,mid-conversation-system-2026-04-07,advanced-tool-use-2025-11-20,effort-2025-11-24,extended-cache-ttl-2025-04-11",
|
|
26
26
|
"content-type": "application/json",
|
|
27
27
|
"user-agent": "claude-cli/2.1.160 (external, cli)",
|
|
28
28
|
connection: "keep-alive",
|
package/src/utils/oauth/index.ts
CHANGED
|
@@ -102,6 +102,21 @@ const builtInOAuthProviders: OAuthProviderInfo[] = [
|
|
|
102
102
|
name: "Xiaomi MiMo",
|
|
103
103
|
available: true,
|
|
104
104
|
},
|
|
105
|
+
{
|
|
106
|
+
id: "xiaomi-token-plan-sgp",
|
|
107
|
+
name: "Xiaomi Token Plan (Singapore)",
|
|
108
|
+
available: true,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: "xiaomi-token-plan-ams",
|
|
112
|
+
name: "Xiaomi Token Plan (Europe)",
|
|
113
|
+
available: true,
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: "xiaomi-token-plan-cn",
|
|
117
|
+
name: "Xiaomi Token Plan (China)",
|
|
118
|
+
available: true,
|
|
119
|
+
},
|
|
105
120
|
{
|
|
106
121
|
id: "firepass",
|
|
107
122
|
name: "Fire Pass (Fireworks Kimi K2.6 Turbo subscription)",
|
|
@@ -364,6 +379,9 @@ export async function refreshOAuthToken(
|
|
|
364
379
|
case "ollama":
|
|
365
380
|
case "ollama-cloud":
|
|
366
381
|
case "xiaomi":
|
|
382
|
+
case "xiaomi-token-plan-sgp":
|
|
383
|
+
case "xiaomi-token-plan-ams":
|
|
384
|
+
case "xiaomi-token-plan-cn":
|
|
367
385
|
case "zai":
|
|
368
386
|
case "zhipu-coding-plan":
|
|
369
387
|
case "qianfan":
|
package/src/utils/oauth/types.ts
CHANGED
|
@@ -4,10 +4,8 @@
|
|
|
4
4
|
* Xiaomi MiMo provides OpenAI-compatible models via
|
|
5
5
|
* https://api.xiaomimimo.com/v1.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* 2. User copies their API key
|
|
10
|
-
* 3. User pastes the API key into the CLI
|
|
7
|
+
* Standard Xiaomi login opens the pay-as-you-go API key console. Token Plan
|
|
8
|
+
* login opens plan management so users copy the regional `tp-...` key.
|
|
11
9
|
*/
|
|
12
10
|
|
|
13
11
|
import type { OAuthController } from "./types";
|
|
@@ -15,13 +13,34 @@ import type { OAuthController } from "./types";
|
|
|
15
13
|
const PROVIDER_ID = "xiaomi";
|
|
16
14
|
const PROVIDER_NAME = "Xiaomi MiMo";
|
|
17
15
|
const STANDARD_AUTH_URL = "https://platform.xiaomimimo.com/#/console/api-keys";
|
|
16
|
+
const TOKEN_PLAN_AUTH_URL = "https://platform.xiaomimimo.com/console/plan-manage";
|
|
18
17
|
const STANDARD_API_BASE_URL = "https://api.xiaomimimo.com/v1";
|
|
19
|
-
const TOKEN_PLAN_SGP_API_BASE_URL = "https://token-plan-sgp.xiaomimimo.com/v1";
|
|
20
|
-
const TOKEN_PLAN_AMS_API_BASE_URL = "https://token-plan-ams.xiaomimimo.com/v1";
|
|
21
|
-
const TOKEN_PLAN_CN_API_BASE_URL = "https://token-plan-cn.xiaomimimo.com/v1";
|
|
22
18
|
const TOKEN_PLAN_KEY_PREFIX = "tp-";
|
|
23
19
|
const STANDARD_VALIDATION_MODEL = "mimo-v2-flash";
|
|
24
20
|
const TOKEN_PLAN_VALIDATION_MODEL = "mimo-v2.5";
|
|
21
|
+
const TOKEN_PLAN_SGP_API_BASE_URL = "https://token-plan-sgp.xiaomimimo.com/v1";
|
|
22
|
+
const TOKEN_PLAN_AMS_API_BASE_URL = "https://token-plan-ams.xiaomimimo.com/v1";
|
|
23
|
+
const TOKEN_PLAN_CN_API_BASE_URL = "https://token-plan-cn.xiaomimimo.com/v1";
|
|
24
|
+
|
|
25
|
+
/** Region codes accepted by the Xiaomi Token Plan login flow. */
|
|
26
|
+
export type XiaomiTokenPlanRegion = "sgp" | "ams" | "cn";
|
|
27
|
+
|
|
28
|
+
type XiaomiValidationEndpoint = {
|
|
29
|
+
baseUrl: string;
|
|
30
|
+
model: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const TOKEN_PLAN_VALIDATION_ENDPOINTS: Record<XiaomiTokenPlanRegion, XiaomiValidationEndpoint> = {
|
|
34
|
+
sgp: { baseUrl: TOKEN_PLAN_SGP_API_BASE_URL, model: TOKEN_PLAN_VALIDATION_MODEL },
|
|
35
|
+
ams: { baseUrl: TOKEN_PLAN_AMS_API_BASE_URL, model: TOKEN_PLAN_VALIDATION_MODEL },
|
|
36
|
+
cn: { baseUrl: TOKEN_PLAN_CN_API_BASE_URL, model: TOKEN_PLAN_VALIDATION_MODEL },
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const TOKEN_PLAN_REGION_NAMES: Record<XiaomiTokenPlanRegion, string> = {
|
|
40
|
+
sgp: "Singapore",
|
|
41
|
+
ams: "Europe",
|
|
42
|
+
cn: "China",
|
|
43
|
+
};
|
|
25
44
|
|
|
26
45
|
function isTokenPlanKey(apiKey: string): boolean {
|
|
27
46
|
return apiKey.startsWith(TOKEN_PLAN_KEY_PREFIX);
|
|
@@ -29,16 +48,22 @@ function isTokenPlanKey(apiKey: string): boolean {
|
|
|
29
48
|
|
|
30
49
|
const VALIDATION_TIMEOUT_MS = 15_000;
|
|
31
50
|
|
|
32
|
-
async function validateXiaomiApiKey(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
:
|
|
51
|
+
async function validateXiaomiApiKey(
|
|
52
|
+
apiKey: string,
|
|
53
|
+
signal?: AbortSignal,
|
|
54
|
+
tokenPlanRegion?: XiaomiTokenPlanRegion,
|
|
55
|
+
): Promise<void> {
|
|
56
|
+
// Region-specific Token Plan logins must validate against the selected
|
|
57
|
+
// cluster. Generic Xiaomi login keeps the historical SGP → AMS → CN fallback.
|
|
58
|
+
const endpoints = tokenPlanRegion
|
|
59
|
+
? [TOKEN_PLAN_VALIDATION_ENDPOINTS[tokenPlanRegion]]
|
|
60
|
+
: isTokenPlanKey(apiKey)
|
|
61
|
+
? [
|
|
62
|
+
TOKEN_PLAN_VALIDATION_ENDPOINTS.sgp,
|
|
63
|
+
TOKEN_PLAN_VALIDATION_ENDPOINTS.ams,
|
|
64
|
+
TOKEN_PLAN_VALIDATION_ENDPOINTS.cn,
|
|
65
|
+
]
|
|
66
|
+
: [{ baseUrl: STANDARD_API_BASE_URL, model: STANDARD_VALIDATION_MODEL }];
|
|
42
67
|
|
|
43
68
|
let lastError: Error | null = null;
|
|
44
69
|
|
|
@@ -137,3 +162,33 @@ export async function loginXiaomi(options: OAuthController): Promise<string> {
|
|
|
137
162
|
await validateXiaomiApiKey(trimmed, options.signal);
|
|
138
163
|
return trimmed;
|
|
139
164
|
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Login to a regional Xiaomi Token Plan endpoint.
|
|
168
|
+
*
|
|
169
|
+
* Prompts for a token-plan API key and validates it against the selected region.
|
|
170
|
+
*/
|
|
171
|
+
export async function loginXiaomiTokenPlan(options: OAuthController, region: XiaomiTokenPlanRegion): Promise<string> {
|
|
172
|
+
if (!options.onPrompt) {
|
|
173
|
+
throw new Error(`Xiaomi Token Plan (${TOKEN_PLAN_REGION_NAMES[region]}) login requires onPrompt callback`);
|
|
174
|
+
}
|
|
175
|
+
options.onAuth?.({
|
|
176
|
+
url: TOKEN_PLAN_AUTH_URL,
|
|
177
|
+
instructions: `Copy your token-plan API key for the ${TOKEN_PLAN_REGION_NAMES[region]} region`,
|
|
178
|
+
});
|
|
179
|
+
const apiKey = await options.onPrompt({
|
|
180
|
+
message: `Paste your Xiaomi Token Plan ${TOKEN_PLAN_REGION_NAMES[region]} API key (tp-...)`,
|
|
181
|
+
placeholder: "tp-...",
|
|
182
|
+
});
|
|
183
|
+
if (options.signal?.aborted) {
|
|
184
|
+
throw new Error("Login cancelled");
|
|
185
|
+
}
|
|
186
|
+
const trimmed = apiKey.trim();
|
|
187
|
+
if (!trimmed) {
|
|
188
|
+
throw new Error("API key is required");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
options.onProgress?.(`Validating Xiaomi Token Plan (${TOKEN_PLAN_REGION_NAMES[region]}) API key...`);
|
|
192
|
+
await validateXiaomiApiKey(trimmed, options.signal, region);
|
|
193
|
+
return trimmed;
|
|
194
|
+
}
|