@oh-my-pi/pi-ai 13.7.6 → 13.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,7 +2,54 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [13.9.1] - 2026-03-05
6
+ ### Breaking Changes
7
+
8
+ - Removed `THINKING_LEVELS`, `ALL_THINKING_LEVELS`, `ALL_THINKING_MODES`, `THINKING_MODE_DESCRIPTIONS`, and `THINKING_MODE_LABELS` exports
9
+ - Renamed `formatThinking()` to `getThinkingMetadata()` with changed return type from string to `ThinkingMetadata` object
10
+ - Renamed `getAvailableThinkingLevel()` to `getAvailableThinkingLevels()` and added default parameter
11
+ - Renamed `getAvailableThinkingEffort()` to `getAvailableThinkingEfforts()` and added default parameter
12
+
13
+ ### Added
14
+
15
+ - Added `ThinkingMetadata` type to provide structured access to thinking mode information (value, label, description)
16
+
17
+ ## [13.9.0] - 2026-03-05
18
+ ### Added
19
+
20
+ - Exported new thinking module with `ThinkingEffort`, `ThinkingLevel`, and `ThinkingMode` types for managing reasoning effort levels
21
+ - Added `getAvailableThinkingEffort()` function to determine supported thinking effort levels based on model capabilities
22
+ - Added `parseThinkingEffort()`, `parseThinkingLevel()`, and `parseThinkingMode()` functions for parsing thinking configuration strings
23
+ - Added `THINKING_LEVELS`, `ALL_THINKING_LEVELS`, and `ALL_THINKING_MODES` constants for iterating over available thinking options
24
+ - Added `THINKING_MODE_DESCRIPTIONS` and `THINKING_MODE_LABELS` for displaying thinking modes in user interfaces
25
+ - Added `formatThinking()` function to format thinking modes as compact display labels
26
+
27
+ ### Changed
28
+
29
+ - Refactored thinking level handling to distinguish between `ThinkingEffort` (provider-level, no "off") and `ThinkingLevel` (user-facing, includes "off")
30
+ - Updated `ThinkingBudgets` type to use `ThinkingEffort` instead of `ThinkingLevel` for more precise token budget configuration
31
+ - Improved reasoning option handling to explicitly support "off" value for disabling reasoning across all providers
32
+ - Simplified thinking effort mapping logic by centralizing provider-specific clamping behavior
33
+
34
+ ## [13.7.8] - 2026-03-04
35
+
36
+ ### Added
37
+
38
+ - Added ZenMux provider support with mixed API routing: Anthropic-owned models discovered from `https://zenmux.ai/api/v1/models` now use the Anthropic transport (`https://zenmux.ai/api/anthropic`), while other ZenMux models use the OpenAI-compatible transport.
39
+
40
+ ## [13.7.7] - 2026-03-04
41
+
42
+ ### Changed
43
+
44
+ - Modified response ID normalization to preserve existing item ID prefixes when truncating oversized IDs
45
+ - Updated tool call ID normalization to use `fc_` prefix for generated item IDs instead of `item_` prefix
46
+
47
+ ### Fixed
48
+
49
+ - Fixed handling of reasoning item IDs to remain untouched during response normalization while function call IDs are properly normalized
50
+
5
51
  ## [13.7.2] - 2026-03-04
52
+
6
53
  ### Added
7
54
 
8
55
  - Added support for Kagi API key authentication via `login kagi` command
@@ -15,6 +62,7 @@
15
62
  - Tool schema compilation is now cached per schema identity, eliminating redundant recompilation on every tool call
16
63
 
17
64
  ## [13.6.0] - 2026-03-03
65
+
18
66
  ### Added
19
67
 
20
68
  - Added Anthropic Foundry gateway mode controlled by `CLAUDE_CODE_USE_FOUNDRY`, with support for `FOUNDRY_BASE_URL`, `ANTHROPIC_FOUNDRY_API_KEY`, `ANTHROPIC_CUSTOM_HEADERS`, and optional mTLS material (`CLAUDE_CODE_CLIENT_CERT`, `CLAUDE_CODE_CLIENT_KEY`, `NODE_EXTRA_CA_CERTS`)
@@ -27,6 +75,7 @@
27
75
  - Anthropic auth base-URL fallback now prefers `FOUNDRY_BASE_URL` when `CLAUDE_CODE_USE_FOUNDRY` is enabled
28
76
 
29
77
  ## [13.5.8] - 2026-03-02
78
+
30
79
  ### Fixed
31
80
 
32
81
  - Fixed schema compatibility issue where patternProperties in tool parameters caused failures when converting to legacy Antigravity format
@@ -43,6 +92,7 @@
43
92
  - Anthropic cache-control normalization now removes later `ttl: "1h"` entries when a default/5m block has already appeared earlier in evaluation order
44
93
 
45
94
  ## [13.5.3] - 2026-03-01
95
+
46
96
  ### Fixed
47
97
 
48
98
  - Fixed tool argument coercion to handle malformed JSON with trailing wrapper braces by parsing leading JSON containers
package/README.md CHANGED
@@ -68,6 +68,7 @@ Unified LLM API with automatic model discovery, provider configuration, token an
68
68
  - **zAI** (requires `ZAI_API_KEY`)
69
69
  - **MiniMax Coding Plan** (requires `MINIMAX_CODE_API_KEY` or `MINIMAX_CODE_CN_API_KEY`)
70
70
  - **Xiaomi MiMo** (requires `XIAOMI_API_KEY`)
71
+ - **ZenMux** (requires `ZENMUX_API_KEY`)
71
72
  - **Qwen Portal** (supports `QWEN_OAUTH_TOKEN` or `QWEN_PORTAL_API_KEY`)
72
73
  - **Cloudflare AI Gateway** (requires `CLOUDFLARE_AI_GATEWAY_API_KEY` and provider-specific gateway base URL)
73
74
  - **Ollama** (local OpenAI-compatible runtime; optional `OLLAMA_API_KEY`)
@@ -929,6 +930,7 @@ In Node.js environments, you can set environment variables to avoid passing API
929
930
  | zAI | `ZAI_API_KEY` |
930
931
  | MiniMax Code | `MINIMAX_CODE_API_KEY` (international) or `MINIMAX_CODE_CN_API_KEY` (China) |
931
932
  | Xiaomi MiMo | `XIAOMI_API_KEY` |
933
+ | ZenMux | `ZENMUX_API_KEY` |
932
934
  | vLLM | `VLLM_API_KEY` |
933
935
  | Cloudflare AI Gateway | `CLOUDFLARE_AI_GATEWAY_API_KEY` |
934
936
  | GitHub Copilot | `COPILOT_GITHUB_TOKEN` or `GH_TOKEN` or `GITHUB_TOKEN` |
@@ -950,6 +952,8 @@ Provider endpoint defaults for the current OpenAI-compatible integrations:
950
952
  - Hugging Face Inference: `https://router.huggingface.co/v1`
951
953
  - Venice: `https://api.venice.ai/api/v1`
952
954
  - Xiaomi MiMo: `https://api.xiaomimimo.com/anthropic`
955
+ - ZenMux (OpenAI): `https://zenmux.ai/api/v1`
956
+ - ZenMux (Anthropic models): `https://zenmux.ai/api/anthropic`
953
957
  - vLLM: `http://127.0.0.1:8000/v1`
954
958
  - Ollama: local OpenAI-compatible runtime
955
959
  - LiteLLM: `http://localhost:4000/v1`
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-ai",
4
- "version": "13.7.6",
4
+ "version": "13.9.1",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://github.com/can1357/oh-my-pi",
7
7
  "author": "Can Boluk",
@@ -41,7 +41,7 @@
41
41
  "@aws-sdk/client-bedrock-runtime": "^3",
42
42
  "@bufbuild/protobuf": "^2.11",
43
43
  "@google/genai": "^1.43",
44
- "@oh-my-pi/pi-utils": "13.7.6",
44
+ "@oh-my-pi/pi-utils": "13.9.1",
45
45
  "@sinclair/typebox": "^0.34",
46
46
  "@smithy/node-http-handler": "^4.4",
47
47
  "ajv": "^8.18",
@@ -64,6 +64,7 @@ import { loginVenice } from "./utils/oauth/venice";
64
64
  import { loginVllm } from "./utils/oauth/vllm";
65
65
  import { loginXiaomi } from "./utils/oauth/xiaomi";
66
66
  import { loginZai } from "./utils/oauth/zai";
67
+ import { loginZenMux } from "./utils/oauth/zenmux";
67
68
 
68
69
  // ─────────────────────────────────────────────────────────────────────────────
69
70
  // Credential Types
@@ -488,12 +489,7 @@ export class AuthStorage {
488
489
  */
489
490
  #getHashedIndex(sessionId: string, total: number): number {
490
491
  if (total <= 1) return 0;
491
- let hash = 2166136261; // FNV offset basis
492
- for (let i = 0; i < sessionId.length; i++) {
493
- hash ^= sessionId.charCodeAt(i);
494
- hash = Math.imul(hash, 16777619); // FNV prime
495
- }
496
- return (hash >>> 0) % total;
492
+ return Bun.hash.xxHash32(sessionId) % total;
497
493
  }
498
494
 
499
495
  /**
@@ -920,6 +916,11 @@ export class AuthStorage {
920
916
  await saveApiKeyCredential(apiKey);
921
917
  return;
922
918
  }
919
+ case "zenmux": {
920
+ const apiKey = await loginZenMux(ctrl);
921
+ await saveApiKeyCredential(apiKey);
922
+ return;
923
+ }
923
924
  default: {
924
925
  const customProvider = getOAuthProvider(provider);
925
926
  if (!customProvider) {
@@ -1490,12 +1491,33 @@ export class AuthStorage {
1490
1491
  const order = this.#getCredentialOrder(providerKey, sessionId, credentials.length);
1491
1492
  const strategy = this.#rankingStrategyResolver?.(provider);
1492
1493
  const checkUsage = strategy !== undefined && credentials.length > 1;
1493
- const candidates = checkUsage
1494
- ? await this.#rankOAuthSelections({ providerKey, provider, order, credentials, options, strategy })
1494
+ const sessionCredential = this.#getSessionCredential(provider, sessionId);
1495
+ const sessionPreferredIndex = sessionCredential?.type === "oauth" ? sessionCredential.index : undefined;
1496
+ // Skip ranking only when the session already has a working preferred credential — re-ranking
1497
+ // mid-session causes account switches that cold-start the server-side prompt cache. New sessions
1498
+ // (no preference) and sessions whose preferred is blocked still rank, so we pick the account
1499
+ // with the most headroom proactively and fall back intelligently when rate-limited.
1500
+ const sessionPreferredIsAvailable =
1501
+ sessionPreferredIndex !== undefined && !this.#isCredentialBlocked(providerKey, sessionPreferredIndex);
1502
+ const shouldRank = checkUsage && !sessionPreferredIsAvailable;
1503
+ const candidates = shouldRank
1504
+ ? await this.#rankOAuthSelections({ providerKey, provider, order, credentials, options, strategy: strategy! })
1495
1505
  : order
1496
1506
  .map(idx => credentials[idx])
1497
1507
  .filter((selection): selection is { credential: OAuthCredential; index: number } => Boolean(selection))
1498
1508
  .map(selection => ({ selection, usage: null, usageChecked: false }));
1509
+
1510
+ if (sessionPreferredIndex !== undefined) {
1511
+ const sessionPreferredCandidate = candidates.findIndex(
1512
+ candidate =>
1513
+ !this.#isCredentialBlocked(providerKey, candidate.selection.index) &&
1514
+ candidate.selection.index === sessionPreferredIndex,
1515
+ );
1516
+ if (sessionPreferredCandidate > 0) {
1517
+ const [preferred] = candidates.splice(sessionPreferredCandidate, 1);
1518
+ candidates.unshift(preferred);
1519
+ }
1520
+ }
1499
1521
  const fallback = candidates[0];
1500
1522
 
1501
1523
  for (const candidate of candidates) {
package/src/cli.ts CHANGED
@@ -15,6 +15,7 @@ import { loginNanoGPT } from "./utils/oauth/nanogpt";
15
15
  import { loginOpenAICodex } from "./utils/oauth/openai-codex";
16
16
  import type { OAuthCredentials, OAuthProvider } from "./utils/oauth/types";
17
17
  import { loginZai } from "./utils/oauth/zai";
18
+ import { loginZenMux } from "./utils/oauth/zenmux";
18
19
 
19
20
  const PROVIDERS = getOAuthProviders();
20
21
 
@@ -220,6 +221,22 @@ async function login(provider: OAuthProvider): Promise<void> {
220
221
  return;
221
222
  }
222
223
 
224
+ case "zenmux": {
225
+ const apiKey = await loginZenMux({
226
+ onAuth(info) {
227
+ const { url, instructions } = info;
228
+ console.log(`\nOpen this URL in your browser:\n${url}`);
229
+ if (instructions) console.log(instructions);
230
+ console.log();
231
+ },
232
+ onPrompt(p) {
233
+ return promptFn(`${p.message}${p.placeholder ? ` (${p.placeholder})` : ""}:`);
234
+ },
235
+ });
236
+ storage.saveApiKey(provider, apiKey);
237
+ console.log(`\nAPI key saved to ~/.omp/agent/agent.db`);
238
+ return;
239
+ }
223
240
  case "minimax-code": {
224
241
  const apiKey = await loginMiniMaxCode({
225
242
  onAuth(info) {
@@ -294,6 +311,7 @@ Providers:
294
311
  minimax-code MiniMax Coding Plan (International)
295
312
  minimax-code-cn MiniMax Coding Plan (China)
296
313
  cursor Cursor (Claude, GPT, etc.)
314
+ zenmux ZenMux
297
315
 
298
316
  Examples:
299
317
  bunx @oh-my-pi/pi-ai login # interactive provider selection
package/src/index.ts CHANGED
@@ -21,6 +21,7 @@ export * from "./providers/openai-responses";
21
21
  export * from "./providers/synthetic";
22
22
  export * from "./rate-limit-utils";
23
23
  export * from "./stream";
24
+ export * from "./thinking";
24
25
  export * from "./types";
25
26
  export * from "./usage";
26
27
  export * from "./usage/claude";