@oh-my-pi/pi-ai 15.8.3 → 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 CHANGED
@@ -2,6 +2,29 @@
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
+
20
+ ## [15.9.0] - 2026-06-04
21
+
22
+ ### Fixed
23
+
24
+ - Fixed MiniMax-compatible OpenAI-completions hosts (e.g. `minimax-code-cn/MiniMax-M3`) losing tool-call arguments when the stream delivers `function.arguments` as a complete object instead of the OpenAI JSON-string contract. The streaming buffer previously concatenated the object into a string, coercing it to `[object Object]` and leaving `bash`/`edit` calls with empty or malformed inputs; the tool-call block now holds the object payload directly. ([#1776](https://github.com/can1357/oh-my-pi/issues/1776))
25
+ - Fixed Cloud Code Assist (Gemini / Antigravity) rejecting tool schemas with `Invalid JSON payload received. Unknown name "propertyNames"` (HTTP 400) when a tool exposed a property literally named `properties` (e.g. the Resend MCP `create_contact` tool). The schema normalizer's `insideProperties` flag was re-asserted when descending into such a property's value schema, so Google-unsupported keywords (`propertyNames`, `additionalProperties`, …) nested inside it were never stripped. The flag is now only set when entering a real `properties` map from a schema node, not from within another `properties` map.
26
+ - Fixed local/self-hosted providers leaking machine-specific endpoints into the bundled `models.json`. A `generate-models` run on a machine with a LiteLLM proxy baked 1202 `litellm` models pinned to `http://localhost:4000/v1` into the committed catalog. `litellm` (and `lm-studio`) now join `ollama`/`vllm` in the generator's discovery-only exclusion set, so local providers are never fetched during generation nor written to `models.json` — they are discovered dynamically at runtime instead. LiteLLM model discovery now enriches metadata against models.dev (the same reference source the other gateway providers use) rather than a bundled reference map. Added a regression test pinning the invariant (no local provider blocks, no loopback/private-network `baseUrl`s in the bundled catalog).
27
+
5
28
  ## [15.8.2] - 2026-06-03
6
29
 
7
30
  ### Fixed
@@ -10,6 +33,7 @@
10
33
  - Fixed MiniMax Coding Plan China login opening the international `platform.minimax.io` subscription page instead of the China `platform.minimaxi.com` page.
11
34
 
12
35
  ## [15.8.0] - 2026-06-02
36
+
13
37
  ### Added
14
38
 
15
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;
@@ -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
- * This is not OAuth - it's a simple API key flow:
8
- * 1. Open browser to Xiaomi MiMo API key console
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.8.3",
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.8.3",
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"
@@ -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);