@magpie-community/opencode-zen-free-auth 0.1.13 → 0.1.15
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 +15 -0
- package/package.json +1 -1
- package/provider.mjs +56 -0
package/README.md
CHANGED
|
@@ -20,6 +20,11 @@ magpie plugin login opencode-zen-free
|
|
|
20
20
|
|
|
21
21
|
Enter `public` in the key field. No personal API key is needed.
|
|
22
22
|
|
|
23
|
+
Hosts supporting magpie's `keyless` and `single` auth metadata can enable
|
|
24
|
+
this public identity without a key field and offer Sign in again instead
|
|
25
|
+
of another account. Older hosts and OpenCode keep the public-key flow.
|
|
26
|
+
Personal keys still belong to the built-in OpenCode Zen provider.
|
|
27
|
+
|
|
23
28
|
Whatever is typed in the key field, the plugin signs in with the one
|
|
24
29
|
shared `public` credential: a key made under your own OpenCode account
|
|
25
30
|
does nothing here, and several of them don't make several accounts. Your
|
|
@@ -69,6 +74,16 @@ itself (`bash`, `edit`, `glob`, `grep`, `read`), described as not
|
|
|
69
74
|
available. A call of one of them the agent doesn't have reaches the agent
|
|
70
75
|
like any other.
|
|
71
76
|
|
|
77
|
+
Zen's free tier also tells OpenCode's own requests by the environment
|
|
78
|
+
block every OpenCode prompt ends with (`<env>`, a `Working directory:`
|
|
79
|
+
line, `</env>`) in a system message. Another agent's request without one
|
|
80
|
+
gets a small allowance a day and then `429 FreeUsageLimitError` ("Free
|
|
81
|
+
models work in OpenCode") until 00:00 UTC, while OpenCode's go through.
|
|
82
|
+
So a request whose system messages have no such block gets one added
|
|
83
|
+
after the agent's own prompt (Anthropic `system` and Responses
|
|
84
|
+
`instructions` likewise), its working directory said as unknown; a
|
|
85
|
+
prompt that has one, as OpenCode's does, goes as it is.
|
|
86
|
+
|
|
72
87
|
Now and then Zen closes a streamed Chat Completions reply before its
|
|
73
88
|
`[DONE]`. When the reply has said something and no tool call of it is
|
|
74
89
|
unfinished, it ends there as an ordinary answer, and its text says it was
|
package/package.json
CHANGED
package/provider.mjs
CHANGED
|
@@ -110,6 +110,59 @@ function normalizeTools(body, protocol) {
|
|
|
110
110
|
return { declared };
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
// Zen's free tier serves a request as OpenCode's own only when one of its
|
|
114
|
+
// system messages carries OpenCode's environment block: `<env>`, a
|
|
115
|
+
// `Working directory:` line and `</env>`, as every OpenCode prompt ends.
|
|
116
|
+
// A request without one (another agent's own prompt: Kimi Work's,
|
|
117
|
+
// WorkBuddy's) gets a small allowance a day, then 429 FreeUsageLimitError
|
|
118
|
+
// ("Free models work in OpenCode") until 00:00 UTC, whatever its headers
|
|
119
|
+
// say; the same request with the block added is served (tried against
|
|
120
|
+
// opencode.ai on 2026-10-25, deepseek-v4.1-flash-free and
|
|
121
|
+
// glm-5.3-flash-free). In a user message or a tool description it doesn't
|
|
122
|
+
// count, and `<ENV>` or `working directory:` don't either. The directory
|
|
123
|
+
// isn't known here, and the agent's own prompt says it, so it is told as
|
|
124
|
+
// unknown rather than as a path that would mislead the model.
|
|
125
|
+
const ENVIRONMENT = "<env>\n Working directory: unknown\n</env>";
|
|
126
|
+
const HAS_ENVIRONMENT = /<env>[\s\S]*Working directory:[\s\S]*<\/env>/;
|
|
127
|
+
|
|
128
|
+
function textOf(content) {
|
|
129
|
+
if (typeof content === "string") return content;
|
|
130
|
+
if (!Array.isArray(content)) return "";
|
|
131
|
+
return content.map((part) => (typeof part?.text === "string" ? part.text : "")).join("\n");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function withEnvironment(content) {
|
|
135
|
+
if (typeof content === "string") return content ? `${content}\n\n${ENVIRONMENT}` : ENVIRONMENT;
|
|
136
|
+
if (Array.isArray(content)) return [...content, { type: "text", text: ENVIRONMENT }];
|
|
137
|
+
return ENVIRONMENT;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const SYSTEM_ROLES = ["system", "developer"];
|
|
141
|
+
|
|
142
|
+
export function addEnvironment(body, protocol) {
|
|
143
|
+
if (protocol === "anthropic") {
|
|
144
|
+
if (!HAS_ENVIRONMENT.test(textOf(body.system))) body.system = withEnvironment(body.system);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const messages = protocol === "chat" ? body.messages : body.input;
|
|
148
|
+
const system = Array.isArray(messages)
|
|
149
|
+
? messages.filter((message) => SYSTEM_ROLES.includes(message?.role))
|
|
150
|
+
: [];
|
|
151
|
+
if (system.some((message) => HAS_ENVIRONMENT.test(textOf(message.content)))) return;
|
|
152
|
+
if (protocol === "responses") {
|
|
153
|
+
if (!HAS_ENVIRONMENT.test(textOf(body.instructions)))
|
|
154
|
+
body.instructions = withEnvironment(body.instructions);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
// Added to the agent's own system message, so the prompt stays one
|
|
158
|
+
// system message, the same on every turn (its cache holds); one is put
|
|
159
|
+
// first when the agent sent none.
|
|
160
|
+
const first = system.find((message) => message.role === "system");
|
|
161
|
+
if (first) first.content = withEnvironment(first.content);
|
|
162
|
+
else if (Array.isArray(messages)) messages.unshift({ role: "system", content: ENVIRONMENT });
|
|
163
|
+
else body.messages = [{ role: "system", content: ENVIRONMENT }];
|
|
164
|
+
}
|
|
165
|
+
|
|
113
166
|
const ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
114
167
|
let lastStamp = 0;
|
|
115
168
|
let counter = 0;
|
|
@@ -252,11 +305,13 @@ async function server(_input, options = {}) {
|
|
|
252
305
|
},
|
|
253
306
|
auth: {
|
|
254
307
|
provider: PROVIDER,
|
|
308
|
+
single: true,
|
|
255
309
|
icon: "https://opencode.ai/favicon-v3.ico",
|
|
256
310
|
maxConcurrency: 2,
|
|
257
311
|
methods: [
|
|
258
312
|
{
|
|
259
313
|
type: "api",
|
|
314
|
+
keyless: true,
|
|
260
315
|
label: "Zen 免费访问(密钥填 public;自己的 OpenCode Key 请加到内置的 OpenCode Zen)",
|
|
261
316
|
placeholder: "public",
|
|
262
317
|
async authorize() {
|
|
@@ -317,6 +372,7 @@ async function server(_input, options = {}) {
|
|
|
317
372
|
const streaming = body.stream === true;
|
|
318
373
|
body.stream = true;
|
|
319
374
|
const toolPolicy = normalizeTools(body, protocol);
|
|
375
|
+
addEnvironment(body, protocol);
|
|
320
376
|
if (protocol === "chat")
|
|
321
377
|
body.stream_options = { ...body.stream_options, include_usage: true };
|
|
322
378
|
// Only what OpenCode 1.19.0 itself sends to its gateway, so a
|