@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 +16 -0
- package/README.md +4 -0
- package/package.json +2 -2
- package/src/auth-storage.ts +30 -8
- package/src/cli.ts +18 -0
- package/src/models.json +18569 -16765
- package/src/provider-models/descriptors.ts +7 -0
- package/src/provider-models/openai-compat.ts +152 -5
- package/src/providers/azure-openai-responses.ts +2 -5
- package/src/providers/openai-codex-responses.ts +1 -4
- package/src/providers/openai-completions.ts +7 -1
- package/src/providers/openai-responses.ts +2 -5
- package/src/stream.ts +1 -0
- package/src/types.ts +1 -0
- package/src/utils/oauth/index.ts +9 -0
- package/src/utils/oauth/types.ts +1 -0
- package/src/utils/oauth/zenmux.ts +51 -0
- package/src/utils.ts +18 -2
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.
|
|
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.
|
|
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",
|
package/src/auth-storage.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
1494
|
-
|
|
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
|