pi-multikey 1.4.0 → 1.5.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/README.md +2 -0
- package/README.zh.md +2 -0
- package/config.ts +21 -0
- package/index.ts +2 -2
- package/package.json +1 -1
- package/probe.ts +6 -4
package/README.md
CHANGED
|
@@ -50,6 +50,8 @@ Built-in presets decouple "model settings" from "keys". The data comes from b.ai
|
|
|
50
50
|
|
|
51
51
|
Endpoint `https://opencode.ai/zen/v1`; keys from [opencode.ai/auth](https://opencode.ai/auth) → workspace Keys. Context / max-output are the **Zen free-tier serving limits** (consistent across models.dev `opencode` provider + pi's built-in opencode catalog); the raw models are bigger — MiMo V2.5 = 1M ctx, Hy3 = 262K ctx. `muse-spark-1.2-contributor-free` uses the OpenAI **Responses** API endpoint; the other seven use chat completions.
|
|
52
52
|
|
|
53
|
+
Requests to this endpoint automatically carry the `User-Agent` header that opencode.ai expects (`opencode/1.15.0 ai-sdk/provider-utils/4.0.23 runtime/bun/1.3.13`) — appended whenever a pool's baseUrl is `https://opencode.ai/zen/v1`, on both probes and live requests.
|
|
54
|
+
|
|
53
55
|
| Model | ctx / max-out | Modalities | Supported thinking levels |
|
|
54
56
|
|---|---|---|---|
|
|
55
57
|
| big-pickle | 200K / 32K | text | always-on (no thinkingLevelMap, like pi's catalog) |
|
package/README.zh.md
CHANGED
|
@@ -56,6 +56,8 @@ DeepSeek / Tencent / 小米官方文档,并对每个 thinking 档位做过实
|
|
|
56
56
|
原始模型更大——MiMo V2.5 = 1M ctx,Hy3 = 262K ctx。
|
|
57
57
|
`muse-spark-1.2-contributor-free` 使用 OpenAI **Responses** API;其余七个使用 chat completions。
|
|
58
58
|
|
|
59
|
+
对该端点的请求会自动携带 opencode.ai 期望的 `User-Agent` 头(`opencode/1.15.0 ai-sdk/provider-utils/4.0.23 runtime/bun/1.3.13`)——只要 pool 的 baseUrl 是 `https://opencode.ai/zen/v1`,探测和真实请求都会附加。
|
|
60
|
+
|
|
59
61
|
| 模型 | ctx / max-out | 模态 | 生效 thinking 档位 |
|
|
60
62
|
|---|---|---|---|
|
|
61
63
|
| big-pickle | 200K / 32K | text | 始终思考(无 thinkingLevelMap,与 pi 内置目录一致) |
|
package/config.ts
CHANGED
|
@@ -339,3 +339,24 @@ export function maskKey(key: string): string {
|
|
|
339
339
|
if (key.length <= 10) return "…";
|
|
340
340
|
return `${key.slice(0, 6)}…${key.slice(-4)}`;
|
|
341
341
|
}
|
|
342
|
+
|
|
343
|
+
// ── Endpoint-required headers ────────────────────────────────────────────────
|
|
344
|
+
|
|
345
|
+
/** OpenCode Zen free tier endpoint that requires a specific User-Agent header. */
|
|
346
|
+
const OPENCODE_ZEN_BASE_URL = "https://opencode.ai/zen/v1";
|
|
347
|
+
const OPENCODE_ZEN_USER_AGENT =
|
|
348
|
+
"opencode/1.15.0 ai-sdk/provider-utils/4.0.23 runtime/bun/1.3.13";
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Headers that must be sent to known endpoints.
|
|
352
|
+
*
|
|
353
|
+
* Currently: OpenCode Zen's free tier endpoint requires this exact User-Agent;
|
|
354
|
+
* anything else returns an empty object.
|
|
355
|
+
*
|
|
356
|
+
* The comparison is case-insensitive and tolerates a trailing slash.
|
|
357
|
+
*/
|
|
358
|
+
export function endpointHeaders(baseUrl: string): Record<string, string> {
|
|
359
|
+
const normalized = baseUrl.replace(/\/+$/, "").toLowerCase();
|
|
360
|
+
if (normalized === OPENCODE_ZEN_BASE_URL) return { "User-Agent": OPENCODE_ZEN_USER_AGENT };
|
|
361
|
+
return {};
|
|
362
|
+
}
|
package/index.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
13
13
|
import { getApiProvider, type Api } from "@earendil-works/pi-ai";
|
|
14
|
-
import { configPath, loadConfig, saveConfig, toProviderModels, type KeypoolConfig, type PoolConfig } from "./config.ts";
|
|
14
|
+
import { configPath, endpointHeaders, loadConfig, saveConfig, toProviderModels, type KeypoolConfig, type PoolConfig } from "./config.ts";
|
|
15
15
|
import { KeyPool } from "./pool.ts";
|
|
16
16
|
import { createRotatingStreamSimple } from "./stream.ts";
|
|
17
17
|
import { runManager, type ManagerHooks } from "./manage.ts";
|
|
@@ -69,7 +69,7 @@ export default function multikey(pi: ExtensionAPI) {
|
|
|
69
69
|
// Real keys are injected per-request by the rotating stream function.
|
|
70
70
|
apiKey: "multikey-managed",
|
|
71
71
|
api,
|
|
72
|
-
headers: pool.headers,
|
|
72
|
+
headers: { ...pool.headers, ...endpointHeaders(pool.baseUrl) },
|
|
73
73
|
models: toProviderModels(pool),
|
|
74
74
|
streamSimple: createRotatingStreamSimple(keyPool, api, notify),
|
|
75
75
|
});
|
package/package.json
CHANGED
package/probe.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { endpointHeaders } from "./config.ts";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Endpoint probing: auto-detect the auth header style and fetch the model list.
|
|
3
5
|
*
|
|
@@ -57,11 +59,11 @@ function authHeaders(style: AuthStyle, key: string): Record<string, string> {
|
|
|
57
59
|
return style === "bearer" ? { Authorization: `Bearer ${key}` } : { "x-api-key": key };
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
async function fetchJson(url: string, headers: Record<string, string>, timeoutMs: number): Promise<{ status: number; body?: unknown }> {
|
|
62
|
+
async function fetchJson(url: string, headers: Record<string, string>, timeoutMs: number, baseUrl?: string): Promise<{ status: number; body?: unknown }> {
|
|
61
63
|
try {
|
|
62
64
|
const response = await fetch(url, {
|
|
63
65
|
method: "GET",
|
|
64
|
-
headers: { Accept: "application/json", ...headers },
|
|
66
|
+
headers: { Accept: "application/json", ...endpointHeaders(baseUrl ?? url), ...headers },
|
|
65
67
|
signal: AbortSignal.timeout(timeoutMs),
|
|
66
68
|
});
|
|
67
69
|
let body: unknown;
|
|
@@ -147,7 +149,7 @@ async function chatProbe(baseUrl: string, style: AuthStyle, key: string, modelId
|
|
|
147
149
|
try {
|
|
148
150
|
const response = await fetch(`${trimSlash(baseUrl)}/chat/completions`, {
|
|
149
151
|
method: "POST",
|
|
150
|
-
headers: { "Content-Type": "application/json", ...authHeaders(style, key) },
|
|
152
|
+
headers: { "Content-Type": "application/json", ...endpointHeaders(baseUrl), ...authHeaders(style, key) },
|
|
151
153
|
body: JSON.stringify({ model: modelId, max_tokens: 4, messages: [{ role: "user", content: "ping" }] }),
|
|
152
154
|
signal: AbortSignal.timeout(CHAT_TIMEOUT_MS),
|
|
153
155
|
});
|
|
@@ -186,7 +188,7 @@ export async function probeEndpoint(
|
|
|
186
188
|
let result: { status: number; body?: unknown };
|
|
187
189
|
try {
|
|
188
190
|
emit(`GET ${url} (${style === "bearer" ? "Authorization: Bearer" : "x-api-key"})…`);
|
|
189
|
-
result = await fetchJson(url, authHeaders(style, key), MODELS_TIMEOUT_MS);
|
|
191
|
+
result = await fetchJson(url, authHeaders(style, key), MODELS_TIMEOUT_MS, baseUrl);
|
|
190
192
|
} catch (error) {
|
|
191
193
|
emit(` network error: ${error instanceof Error ? error.message : String(error)}`);
|
|
192
194
|
continue;
|