@magpie-community/opencode-zen-free-auth 0.1.14 → 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.
Files changed (3) hide show
  1. package/README.md +10 -0
  2. package/package.json +1 -1
  3. package/provider.mjs +54 -0
package/README.md CHANGED
@@ -74,6 +74,16 @@ itself (`bash`, `edit`, `glob`, `grep`, `read`), described as not
74
74
  available. A call of one of them the agent doesn't have reaches the agent
75
75
  like any other.
76
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
+
77
87
  Now and then Zen closes a streamed Chat Completions reply before its
78
88
  `[DONE]`. When the reply has said something and no tool call of it is
79
89
  unfinished, it ends there as an ordinary answer, and its text says it was
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magpie-community/opencode-zen-free-auth",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "OpenCode Zen free models through the public credential, with native streaming protocols",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",
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;
@@ -319,6 +372,7 @@ async function server(_input, options = {}) {
319
372
  const streaming = body.stream === true;
320
373
  body.stream = true;
321
374
  const toolPolicy = normalizeTools(body, protocol);
375
+ addEnvironment(body, protocol);
322
376
  if (protocol === "chat")
323
377
  body.stream_options = { ...body.stream_options, include_usage: true };
324
378
  // Only what OpenCode 1.19.0 itself sends to its gateway, so a