opencode-zen-freex 0.2.1 → 0.2.2
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/MAINTAINING.md +171 -0
- package/README.md +45 -11
- package/capture.mjs +203 -0
- package/index.mjs +3 -3
- package/models.mjs +211 -148
- package/package.json +4 -1
- package/provider.mjs +503 -461
- package/stream.mjs +398 -398
- package/tool-response.mjs +881 -881
- package/tools-smoke.mjs +169 -0
package/models.mjs
CHANGED
|
@@ -1,148 +1,211 @@
|
|
|
1
|
-
export const PROVIDER = "opencode-zen-free";
|
|
2
|
-
export const SDK = {
|
|
3
|
-
chat: "@ai-sdk/openai-compatible",
|
|
4
|
-
responses: "@ai-sdk/openai",
|
|
5
|
-
anthropic: "@ai-sdk/anthropic",
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
export function parseProtocolDocs(text) {
|
|
9
|
-
const protocols = {};
|
|
10
|
-
const pattern =
|
|
11
|
-
/\|[^|]+\|\s*`?([^|`\s]+)`?\s*\|\s*`[^`]+\/v1\/(chat\/completions|responses|messages|systemone)`/g;
|
|
12
|
-
for (const match of text.matchAll(pattern)) {
|
|
13
|
-
protocols[match[1]] = {
|
|
14
|
-
"chat/completions": "chat",
|
|
15
|
-
responses: "responses",
|
|
16
|
-
messages: "anthropic",
|
|
17
|
-
systemone: "systemone",
|
|
18
|
-
}[match[2]];
|
|
19
|
-
}
|
|
20
|
-
return protocols;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function sdkProtocol(npm) {
|
|
24
|
-
if (npm?.includes("anthropic")) return "anthropic";
|
|
25
|
-
if (npm === SDK.responses) return "responses";
|
|
26
|
-
if (npm?.includes("openai-compatible")) return "chat";
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function isRetired(model) {
|
|
30
|
-
return (
|
|
31
|
-
model.deprecated === true ||
|
|
32
|
-
["deprecated", "retired", "disabled"].includes(model.status ?? model.lifecycle) ||
|
|
33
|
-
model.deprecated_at != null ||
|
|
34
|
-
model.retirement_date != null
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function isFree(id, model = {}) {
|
|
39
|
-
return (
|
|
40
|
-
!isRetired(model) && (/free/i.test(id) || (model.cost?.input === 0 && model.cost?.output === 0))
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function buildModels(ids, provider, protocols, baseURL) {
|
|
45
|
-
const result = {};
|
|
46
|
-
for (const id of ids) {
|
|
47
|
-
if (typeof id !== "string" || !id || id === "__proto__") continue;
|
|
48
|
-
const raw = provider.models?.[id];
|
|
49
|
-
const model = raw ?? {};
|
|
50
|
-
const protocol = protocols[id] ?? (raw && sdkProtocol(model.provider?.npm ?? provider.npm));
|
|
51
|
-
if (!isFree(id, model) || !Object.hasOwn(SDK, protocol)) continue;
|
|
52
|
-
const input = model.modalities?.input ?? ["text"];
|
|
53
|
-
const output = model.modalities?.output ?? ["text"];
|
|
54
|
-
const modalities = (values) =>
|
|
55
|
-
Object.fromEntries(
|
|
56
|
-
["text", "image", "audio", "video", "pdf"].map((name) => [name, values.includes(name)]),
|
|
57
|
-
);
|
|
58
|
-
const efforts = (model.reasoning_options ?? [])
|
|
59
|
-
.filter((option) => option.type === "effort")
|
|
60
|
-
.flatMap((option) => option.values ?? []);
|
|
61
|
-
result[id] = {
|
|
62
|
-
id,
|
|
63
|
-
providerID: PROVIDER,
|
|
64
|
-
name: model.name ?? id,
|
|
65
|
-
api: { id, url: baseURL, npm: SDK[protocol] },
|
|
66
|
-
limit: { context: 32768, output: 4096, ...model.limit },
|
|
67
|
-
capabilities: {
|
|
68
|
-
temperature: model.temperature ?? true,
|
|
69
|
-
reasoning: model.reasoning ?? false,
|
|
70
|
-
attachment: model.attachment ?? input.some((name) => name !== "text"),
|
|
71
|
-
toolcall: model.tool_call ?? true,
|
|
72
|
-
input: modalities(input),
|
|
73
|
-
output: modalities(output),
|
|
74
|
-
interleaved: model.interleaved ?? false,
|
|
75
|
-
},
|
|
76
|
-
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
|
|
77
|
-
variants: Object.fromEntries(efforts.map((effort) => [effort, { reasoningEffort: effort }])),
|
|
78
|
-
options: {},
|
|
79
|
-
headers: {},
|
|
80
|
-
status: model.status ?? "active",
|
|
81
|
-
release_date: model.release_date ?? "",
|
|
82
|
-
free: true,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
return result;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
],
|
|
118
|
-
},
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
{
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
},
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
1
|
+
export const PROVIDER = "opencode-zen-free";
|
|
2
|
+
export const SDK = {
|
|
3
|
+
chat: "@ai-sdk/openai-compatible",
|
|
4
|
+
responses: "@ai-sdk/openai",
|
|
5
|
+
anthropic: "@ai-sdk/anthropic",
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function parseProtocolDocs(text) {
|
|
9
|
+
const protocols = {};
|
|
10
|
+
const pattern =
|
|
11
|
+
/\|[^|]+\|\s*`?([^|`\s]+)`?\s*\|\s*`[^`]+\/v1\/(chat\/completions|responses|messages|systemone)`/g;
|
|
12
|
+
for (const match of text.matchAll(pattern)) {
|
|
13
|
+
protocols[match[1]] = {
|
|
14
|
+
"chat/completions": "chat",
|
|
15
|
+
responses: "responses",
|
|
16
|
+
messages: "anthropic",
|
|
17
|
+
systemone: "systemone",
|
|
18
|
+
}[match[2]];
|
|
19
|
+
}
|
|
20
|
+
return protocols;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function sdkProtocol(npm) {
|
|
24
|
+
if (npm?.includes("anthropic")) return "anthropic";
|
|
25
|
+
if (npm === SDK.responses) return "responses";
|
|
26
|
+
if (npm?.includes("openai-compatible")) return "chat";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isRetired(model) {
|
|
30
|
+
return (
|
|
31
|
+
model.deprecated === true ||
|
|
32
|
+
["deprecated", "retired", "disabled"].includes(model.status ?? model.lifecycle) ||
|
|
33
|
+
model.deprecated_at != null ||
|
|
34
|
+
model.retirement_date != null
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function isFree(id, model = {}) {
|
|
39
|
+
return (
|
|
40
|
+
!isRetired(model) && (/free/i.test(id) || (model.cost?.input === 0 && model.cost?.output === 0))
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function buildModels(ids, provider, protocols, baseURL) {
|
|
45
|
+
const result = {};
|
|
46
|
+
for (const id of ids) {
|
|
47
|
+
if (typeof id !== "string" || !id || id === "__proto__") continue;
|
|
48
|
+
const raw = provider.models?.[id];
|
|
49
|
+
const model = raw ?? {};
|
|
50
|
+
const protocol = protocols[id] ?? (raw && sdkProtocol(model.provider?.npm ?? provider.npm));
|
|
51
|
+
if (!isFree(id, model) || !Object.hasOwn(SDK, protocol)) continue;
|
|
52
|
+
const input = model.modalities?.input ?? ["text"];
|
|
53
|
+
const output = model.modalities?.output ?? ["text"];
|
|
54
|
+
const modalities = (values) =>
|
|
55
|
+
Object.fromEntries(
|
|
56
|
+
["text", "image", "audio", "video", "pdf"].map((name) => [name, values.includes(name)]),
|
|
57
|
+
);
|
|
58
|
+
const efforts = (model.reasoning_options ?? [])
|
|
59
|
+
.filter((option) => option.type === "effort")
|
|
60
|
+
.flatMap((option) => option.values ?? []);
|
|
61
|
+
result[id] = {
|
|
62
|
+
id,
|
|
63
|
+
providerID: PROVIDER,
|
|
64
|
+
name: model.name ?? id,
|
|
65
|
+
api: { id, url: baseURL, npm: SDK[protocol] },
|
|
66
|
+
limit: { context: 32768, output: 4096, ...model.limit },
|
|
67
|
+
capabilities: {
|
|
68
|
+
temperature: model.temperature ?? true,
|
|
69
|
+
reasoning: model.reasoning ?? false,
|
|
70
|
+
attachment: model.attachment ?? input.some((name) => name !== "text"),
|
|
71
|
+
toolcall: model.tool_call ?? true,
|
|
72
|
+
input: modalities(input),
|
|
73
|
+
output: modalities(output),
|
|
74
|
+
interleaved: model.interleaved ?? false,
|
|
75
|
+
},
|
|
76
|
+
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
|
|
77
|
+
variants: Object.fromEntries(efforts.map((effort) => [effort, { reasoningEffort: effort }])),
|
|
78
|
+
options: {},
|
|
79
|
+
headers: {},
|
|
80
|
+
status: model.status ?? "active",
|
|
81
|
+
release_date: model.release_date ?? "",
|
|
82
|
+
free: true,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// What each free model is asked at, from OpenCode's console docs
|
|
89
|
+
// (services/www/src/docs/content/console/models.mdx) on 2026-10-15: used for
|
|
90
|
+
// a listed model when the docs can't be read, so the catalog or the docs
|
|
91
|
+
// being out of reach never leaves a listed free model out. Jev is a
|
|
92
|
+
// SystemOne decision model, not one to chat with.
|
|
93
|
+
export const bootstrapProtocols = {
|
|
94
|
+
"big-pickle": "chat",
|
|
95
|
+
"space-bunny-free": "chat",
|
|
96
|
+
"cat-jam-free": "chat",
|
|
97
|
+
"longcat-2.5-preview-free": "chat",
|
|
98
|
+
"step-5-preview-free": "chat",
|
|
99
|
+
"mimo-v2.6-flash-free": "chat",
|
|
100
|
+
"nemotron-3.5-lightning-free": "chat",
|
|
101
|
+
"muse-spark-1.3-contributor-free": "responses",
|
|
102
|
+
"jev-1.13-free": "systemone",
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Offline bootstrap metadata from the OpenCode catalog, 2026-10-06; the
|
|
106
|
+
// free models listed since added on 2026-10-15.
|
|
107
|
+
export const bootstrapProvider = {
|
|
108
|
+
npm: SDK.chat,
|
|
109
|
+
models: {
|
|
110
|
+
"cat-jam-free": {
|
|
111
|
+
name: "Cat Jam Free",
|
|
112
|
+
cost: { input: 0, output: 0 },
|
|
113
|
+
limit: { context: 262144, output: 131072 },
|
|
114
|
+
reasoning: true,
|
|
115
|
+
tool_call: true,
|
|
116
|
+
interleaved: { field: "reasoning_content" },
|
|
117
|
+
reasoning_options: [{ type: "effort", values: ["low", "medium", "high", "xhigh", "max"] }],
|
|
118
|
+
},
|
|
119
|
+
"step-5-preview-free": {
|
|
120
|
+
name: "Step 5 Preview Free",
|
|
121
|
+
cost: { input: 0, output: 0 },
|
|
122
|
+
limit: { context: 1000000, input: 1000000, output: 65536 },
|
|
123
|
+
reasoning: true,
|
|
124
|
+
tool_call: true,
|
|
125
|
+
interleaved: { field: "reasoning_content" },
|
|
126
|
+
modalities: { input: ["text", "image", "video"], output: ["text"] },
|
|
127
|
+
reasoning_options: [{ type: "effort", values: ["low", "medium", "high"] }],
|
|
128
|
+
},
|
|
129
|
+
"longcat-2.5-preview-free": {
|
|
130
|
+
name: "LongCat 2.5 Preview Free",
|
|
131
|
+
cost: { input: 0, output: 0 },
|
|
132
|
+
limit: { context: 1000000, output: 131072 },
|
|
133
|
+
reasoning: true,
|
|
134
|
+
tool_call: true,
|
|
135
|
+
interleaved: { field: "reasoning_content" },
|
|
136
|
+
modalities: { input: ["text", "image"], output: ["text"] },
|
|
137
|
+
},
|
|
138
|
+
"mimo-v2.6-flash-free": {
|
|
139
|
+
name: "MiMo-V2.6-Flash Free",
|
|
140
|
+
cost: { input: 0, output: 0 },
|
|
141
|
+
limit: { context: 200000, output: 32000 },
|
|
142
|
+
reasoning: true,
|
|
143
|
+
tool_call: true,
|
|
144
|
+
interleaved: { field: "reasoning_content" },
|
|
145
|
+
modalities: { input: ["text", "image", "audio", "video"], output: ["text"] },
|
|
146
|
+
},
|
|
147
|
+
"nemotron-3.5-lightning-free": {
|
|
148
|
+
name: "Nemotron 3.5 Lightning Free",
|
|
149
|
+
cost: { input: 0, output: 0 },
|
|
150
|
+
limit: { context: 262144, output: 262144 },
|
|
151
|
+
reasoning: true,
|
|
152
|
+
tool_call: true,
|
|
153
|
+
interleaved: { field: "reasoning_content" },
|
|
154
|
+
},
|
|
155
|
+
"big-pickle": {
|
|
156
|
+
name: "Big Pickle",
|
|
157
|
+
cost: { input: 0, output: 0 },
|
|
158
|
+
limit: { context: 200000, input: 160000, output: 32000 },
|
|
159
|
+
reasoning: true,
|
|
160
|
+
tool_call: true,
|
|
161
|
+
interleaved: { field: "reasoning_content" },
|
|
162
|
+
},
|
|
163
|
+
"space-bunny-free": {
|
|
164
|
+
name: "Space Bunny Free",
|
|
165
|
+
cost: { input: 0, output: 0 },
|
|
166
|
+
limit: { context: 1048576, input: 524288, output: 524288 },
|
|
167
|
+
reasoning: true,
|
|
168
|
+
tool_call: true,
|
|
169
|
+
},
|
|
170
|
+
"muse-spark-1.3-contributor-free": {
|
|
171
|
+
name: "Muse Spark 1.3 Free",
|
|
172
|
+
cost: { input: 0, output: 0 },
|
|
173
|
+
provider: { npm: SDK.responses },
|
|
174
|
+
limit: { context: 1048576, output: 131072 },
|
|
175
|
+
reasoning: true,
|
|
176
|
+
tool_call: true,
|
|
177
|
+
modalities: { input: ["text", "image", "video", "pdf", "audio"], output: ["text"] },
|
|
178
|
+
reasoning_options: [
|
|
179
|
+
{ type: "effort", values: ["minimal", "low", "medium", "high", "xhigh"] },
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
export function configModels(models) {
|
|
186
|
+
return Object.fromEntries(
|
|
187
|
+
Object.entries(models).map(([id, model]) => [
|
|
188
|
+
id,
|
|
189
|
+
{
|
|
190
|
+
id,
|
|
191
|
+
name: model.name,
|
|
192
|
+
provider: { npm: model.api.npm, api: model.api.url },
|
|
193
|
+
limit: model.limit,
|
|
194
|
+
reasoning: model.capabilities.reasoning,
|
|
195
|
+
tool_call: model.capabilities.toolcall,
|
|
196
|
+
attachment: model.capabilities.attachment,
|
|
197
|
+
modalities: {
|
|
198
|
+
input: Object.keys(model.capabilities.input).filter(
|
|
199
|
+
(key) => model.capabilities.input[key],
|
|
200
|
+
),
|
|
201
|
+
output: Object.keys(model.capabilities.output).filter(
|
|
202
|
+
(key) => model.capabilities.output[key],
|
|
203
|
+
),
|
|
204
|
+
},
|
|
205
|
+
cost: { input: 0, output: 0 },
|
|
206
|
+
variants: model.variants,
|
|
207
|
+
free: true,
|
|
208
|
+
},
|
|
209
|
+
]),
|
|
210
|
+
);
|
|
211
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-zen-freex",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "OpenCode Zen's free models, for hosts that are not OpenCode: the public credential, automatic discovery and native streaming protocols",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.mjs",
|
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
"models.mjs",
|
|
13
13
|
"stream.mjs",
|
|
14
14
|
"tool-response.mjs",
|
|
15
|
+
"tools-smoke.mjs",
|
|
16
|
+
"capture.mjs",
|
|
15
17
|
"README.md",
|
|
18
|
+
"MAINTAINING.md",
|
|
16
19
|
"LICENSE"
|
|
17
20
|
],
|
|
18
21
|
"keywords": ["opencode", "opencode-plugin", "zen", "free", "magpie"],
|