@oh-my-pi/pi-ai 13.7.5 → 13.8.0

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,22 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [13.7.8] - 2026-03-04
6
+
7
+ ### Added
8
+
9
+ - 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.
10
+
11
+ ## [13.7.7] - 2026-03-04
12
+ ### Changed
13
+
14
+ - Modified response ID normalization to preserve existing item ID prefixes when truncating oversized IDs
15
+ - Updated tool call ID normalization to use `fc_` prefix for generated item IDs instead of `item_` prefix
16
+
17
+ ### Fixed
18
+
19
+ - Fixed handling of reasoning item IDs to remain untouched during response normalization while function call IDs are properly normalized
20
+
5
21
  ## [13.7.2] - 2026-03-04
6
22
  ### Added
7
23
 
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.5",
4
+ "version": "13.8.0",
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.5",
44
+ "@oh-my-pi/pi-utils": "13.8.0",
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