pi-multikey 1.3.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 +6 -3
- package/README.zh.md +6 -3
- package/config.ts +21 -0
- package/index.ts +2 -2
- package/package.json +1 -1
- package/presets.ts +15 -1
- package/probe.ts +6 -4
package/README.md
CHANGED
|
@@ -48,11 +48,14 @@ Built-in presets decouple "model settings" from "keys". The data comes from b.ai
|
|
|
48
48
|
|
|
49
49
|
### OpenCode Zen (free tier)
|
|
50
50
|
|
|
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
|
|
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
|
+
|
|
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.
|
|
52
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) |
|
|
58
|
+
| deepseek-v4-flash-free | 200K / 128K | text | off · low · high · max (mirrors b.ai's deepseek-v4-flash) |
|
|
56
59
|
| mimo-v2.5-free | 200K / 32K | text+image | always-on (no thinkingLevelMap) |
|
|
57
60
|
| hy3-free | 190K / 64K | text | low · medium · high (no off) |
|
|
58
61
|
| ling-3.0-flash-fin-free | 256K / 32K | text | always-on (no thinkingLevelMap) |
|
|
@@ -60,7 +63,7 @@ Endpoint `https://opencode.ai/zen/v1`; keys from [opencode.ai/auth](https://open
|
|
|
60
63
|
| nemotron-3.5-lightning-free | 256K / 256K | text | always-on (no thinkingLevelMap) |
|
|
61
64
|
| muse-spark-1.2-contributor-free | 1M / 128K | text+image | minimal · low · medium · high · xhigh (no off, Responses API) |
|
|
62
65
|
|
|
63
|
-
> All
|
|
66
|
+
> All eight models are free (zero per-token cost) for a limited time while OpenCode collects feedback; data may be used to improve the models (Nemotron free endpoints are NVIDIA trials — don't send confidential data).
|
|
64
67
|
|
|
65
68
|
To add a preset: append one entry to the `PRESETS` array in `presets.ts`.
|
|
66
69
|
|
|
@@ -116,7 +119,7 @@ The detected header style is stored as `"auth": "api-key"` only when the endpoin
|
|
|
116
119
|
│ └─ Delete pool
|
|
117
120
|
├─ Add pool…
|
|
118
121
|
│ ├─ Preset: B.AI all model settings preloaded; paste keys (verified by a probe) and you're done
|
|
119
|
-
│ ├─ Preset: OpenCode Zen free-tier models preloaded (
|
|
122
|
+
│ ├─ Preset: OpenCode Zen free-tier models preloaded (8 models); paste keys and you're done
|
|
120
123
|
│ └─ Custom… id + base URL + keys, then auto-probe, model multi-select, safe defaults
|
|
121
124
|
└─ Reload config from disk
|
|
122
125
|
```
|
package/README.zh.md
CHANGED
|
@@ -54,11 +54,14 @@ DeepSeek / Tencent / 小米官方文档,并对每个 thinking 档位做过实
|
|
|
54
54
|
端点 `https://opencode.ai/zen/v1`;密钥从 [opencode.ai/auth](https://opencode.ai/auth) → workspace Keys 获取。
|
|
55
55
|
上下文/最大输出为 **Zen 免费层限制**(models.dev `opencode` provider 与 pi 内置 opencode 目录一致);
|
|
56
56
|
原始模型更大——MiMo V2.5 = 1M ctx,Hy3 = 262K ctx。
|
|
57
|
-
`muse-spark-1.2-contributor-free` 使用 OpenAI **Responses** API
|
|
57
|
+
`muse-spark-1.2-contributor-free` 使用 OpenAI **Responses** API;其余七个使用 chat completions。
|
|
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`,探测和真实请求都会附加。
|
|
58
60
|
|
|
59
61
|
| 模型 | ctx / max-out | 模态 | 生效 thinking 档位 |
|
|
60
62
|
|---|---|---|---|
|
|
61
63
|
| big-pickle | 200K / 32K | text | 始终思考(无 thinkingLevelMap,与 pi 内置目录一致) |
|
|
64
|
+
| deepseek-v4-flash-free | 200K / 128K | text | off · low · high · max(沿用 b.ai preset 的 deepseek-v4-flash) |
|
|
62
65
|
| mimo-v2.5-free | 200K / 32K | text+image | 始终思考(无 thinkingLevelMap) |
|
|
63
66
|
| hy3-free | 190K / 64K | text | low · medium · high(无 off) |
|
|
64
67
|
| ling-3.0-flash-fin-free | 256K / 32K | text | 始终思考(无 thinkingLevelMap) |
|
|
@@ -66,7 +69,7 @@ DeepSeek / Tencent / 小米官方文档,并对每个 thinking 档位做过实
|
|
|
66
69
|
| nemotron-3.5-lightning-free | 256K / 256K | text | 始终思考(无 thinkingLevelMap) |
|
|
67
70
|
| muse-spark-1.2-contributor-free | 1M / 128K | text+image | minimal · low · medium · high · xhigh(无 off,Responses API) |
|
|
68
71
|
|
|
69
|
-
>
|
|
72
|
+
> 以上八个模型在 OpenCode 收集反馈期间均免费(零 token 费用);数据可能用于改进模型(Nemotron 免费端点为 NVIDIA 试用——请勿提交机密数据)。
|
|
70
73
|
|
|
71
74
|
新增 preset:在 `presets.ts` 的 `PRESETS` 数组里加一项即可。
|
|
72
75
|
|
|
@@ -125,7 +128,7 @@ JSON 后 `Reload config from disk`。
|
|
|
125
128
|
│ └─ Delete pool
|
|
126
129
|
├─ Add pool…
|
|
127
130
|
│ ├─ Preset: B.AI 预置全部模型设定,粘贴 key(自动校验)即可用
|
|
128
|
-
│ ├─ Preset: OpenCode Zen 免费层模型预置(
|
|
131
|
+
│ ├─ Preset: OpenCode Zen 免费层模型预置(8 个模型),粘贴 key 即可用
|
|
129
132
|
│ └─ Custom… 只填 id + Base URL + key,随后自动探测、多选模型、安全默认值
|
|
130
133
|
└─ Reload config from disk
|
|
131
134
|
```
|
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/presets.ts
CHANGED
|
@@ -131,7 +131,7 @@ export const PRESETS: Preset[] = [
|
|
|
131
131
|
{
|
|
132
132
|
id: "opencode-zen",
|
|
133
133
|
name: "OpenCode Zen",
|
|
134
|
-
description: "opencode.ai/zen free tier — Big Pickle, MiMo V2.5, Hy3, Ling 3.0 Fin, Nemotron 3 Ultra/Lightning, Muse Spark 1.2 (
|
|
134
|
+
description: "opencode.ai/zen free tier — Big Pickle, DeepSeek V4 Flash, MiMo V2.5, Hy3, Ling 3.0 Fin, Nemotron 3 Ultra/Lightning, Muse Spark 1.2 (8 free models)",
|
|
135
135
|
defaultPoolId: "zen",
|
|
136
136
|
baseUrl: "https://opencode.ai/zen/v1",
|
|
137
137
|
api: "openai-completions",
|
|
@@ -148,6 +148,20 @@ export const PRESETS: Preset[] = [
|
|
|
148
148
|
maxTokens: 32_000,
|
|
149
149
|
compat: ZEN_CHAT_COMPAT,
|
|
150
150
|
},
|
|
151
|
+
{
|
|
152
|
+
// DeepSeek V4 Flash on the Zen FREE tier — id confirmed via GET /zen/v1/models
|
|
153
|
+
// (deepseek-v4-flash-free); serving limits 200K/128K from models.dev `opencode`
|
|
154
|
+
// provider. Thinking levels mirror the b.ai preset's deepseek-v4-flash
|
|
155
|
+
// (off/low/high/max; medium & xhigh alias to high server-side).
|
|
156
|
+
id: "deepseek-v4-flash-free",
|
|
157
|
+
name: "DeepSeek V4 Flash Free",
|
|
158
|
+
reasoning: true,
|
|
159
|
+
input: ["text"],
|
|
160
|
+
contextWindow: 200_000,
|
|
161
|
+
maxTokens: 128_000,
|
|
162
|
+
thinkingLevelMap: levels({ off: "none", low: "low", high: "high", max: "max" }),
|
|
163
|
+
compat: ZEN_CHAT_COMPAT,
|
|
164
|
+
},
|
|
151
165
|
{
|
|
152
166
|
// Xiaomi MiMo V2.5 omni; raw model is 1M ctx but the Zen FREE tier serves 200K/32K.
|
|
153
167
|
id: "mimo-v2.5-free",
|
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;
|