@namzu/zen 3.1.0 → 4.0.1
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/CHANGELOG.md +85 -0
- package/README.md +33 -14
- package/dist/catalogue/catalogue.d.ts.map +1 -1
- package/dist/catalogue/catalogue.js +6 -0
- package/dist/catalogue/catalogue.js.map +1 -1
- package/dist/catalogue/derive.d.ts +15 -1
- package/dist/catalogue/derive.d.ts.map +1 -1
- package/dist/catalogue/derive.js +73 -10
- package/dist/catalogue/derive.js.map +1 -1
- package/dist/catalogue/experimental-free.d.ts +5 -0
- package/dist/catalogue/experimental-free.d.ts.map +1 -1
- package/dist/catalogue/experimental-free.js +4 -19
- package/dist/catalogue/experimental-free.js.map +1 -1
- package/dist/catalogue/fetch.d.ts +2 -0
- package/dist/catalogue/fetch.d.ts.map +1 -1
- package/dist/catalogue/fetch.js +14 -5
- package/dist/catalogue/fetch.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +22 -13
- package/dist/client.js.map +1 -1
- package/dist/experimental-identity.d.ts +35 -3
- package/dist/experimental-identity.d.ts.map +1 -1
- package/dist/experimental-identity.js +69 -4
- package/dist/experimental-identity.js.map +1 -1
- package/dist/models.d.ts +2 -0
- package/dist/models.d.ts.map +1 -1
- package/dist/models.js +202 -29
- package/dist/models.js.map +1 -1
- package/dist/options.d.ts +14 -0
- package/dist/options.d.ts.map +1 -1
- package/dist/options.js +110 -9
- package/dist/options.js.map +1 -1
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +50 -1
- package/dist/prompt.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/package.json +2 -2
- package/src/catalogue/catalogue.ts +6 -0
- package/src/catalogue/derive.ts +80 -9
- package/src/catalogue/experimental-free.ts +4 -20
- package/src/catalogue/fetch.ts +16 -5
- package/src/client.ts +27 -20
- package/src/experimental-identity.ts +73 -5
- package/src/models.review.json +6 -15
- package/src/models.ts +204 -29
- package/src/options.ts +125 -9
- package/src/prompt.ts +56 -1
- package/src/types.ts +2 -2
|
@@ -1,31 +1,15 @@
|
|
|
1
1
|
import type { ZenModel, ZenService } from '../models.js'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Free models
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* an arbitrary model or opt a caller into Zen Go.
|
|
4
|
+
* Free models: any model with zero input and output price
|
|
5
|
+
* is treated as experimentally available through the anonymous path.
|
|
6
|
+
* No explicit list; the runtime catalogue's price determines availability.
|
|
8
7
|
*/
|
|
9
|
-
const EXPERIMENTAL_FREE_ZEN_IDS: ReadonlySet<string> = new Set([
|
|
10
|
-
'big-pickle',
|
|
11
|
-
'ling-3.0-flash-fin-free',
|
|
12
|
-
'longcat-2.5-preview-free',
|
|
13
|
-
'mimo-v2.5-free',
|
|
14
|
-
'muse-spark-1.3-contributor-free',
|
|
15
|
-
'nemotron-3-ultra-free',
|
|
16
|
-
'nemotron-3.5-lightning-free',
|
|
17
|
-
'space-bunny-free',
|
|
18
|
-
])
|
|
19
|
-
|
|
20
8
|
export function isExperimentalFreeZenModel(
|
|
21
9
|
service: ZenService,
|
|
22
10
|
model: ZenModel | undefined,
|
|
23
11
|
): boolean {
|
|
24
12
|
return (
|
|
25
|
-
service === 'zen' &&
|
|
26
|
-
model !== undefined &&
|
|
27
|
-
EXPERIMENTAL_FREE_ZEN_IDS.has(model.id) &&
|
|
28
|
-
model.inputPrice === 0 &&
|
|
29
|
-
model.outputPrice === 0
|
|
13
|
+
service === 'zen' && model !== undefined && model.inputPrice === 0 && model.outputPrice === 0
|
|
30
14
|
)
|
|
31
15
|
}
|
package/src/catalogue/fetch.ts
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
type ZenCatalogueSources,
|
|
12
12
|
buildZenCatalogue,
|
|
13
13
|
} from './catalogue.js'
|
|
14
|
-
import { SERVICE_PREFIX, ZenCatalogueSourceError } from './derive.js'
|
|
14
|
+
import { SERVICE_PREFIX, ZEN_SERVICES, ZenCatalogueSourceError } from './derive.js'
|
|
15
15
|
|
|
16
16
|
/** The branch of OpenCode's repository the two documentation pages are read from. */
|
|
17
17
|
export const ZEN_DOCS_REF = 'dev'
|
|
@@ -50,7 +50,18 @@ const DEFAULT_TIMEOUT_MS = 15_000
|
|
|
50
50
|
const MIB = 1024 * 1024
|
|
51
51
|
|
|
52
52
|
function docsBase(ref: string): string {
|
|
53
|
-
|
|
53
|
+
// The docs site moved under `services/www`; the two pages are read from
|
|
54
|
+
// there now. `ZEN_SERVICES` carries each page's path under this root.
|
|
55
|
+
return `https://raw.githubusercontent.com/anomalyco/opencode/${encodeURIComponent(
|
|
56
|
+
ref,
|
|
57
|
+
)}/services/www/src/docs/content`
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The URL of one service's documentation page. */
|
|
61
|
+
export function zenDocsUrl(service: ZenService, ref = ZEN_DOCS_REF): string {
|
|
62
|
+
const page = ZEN_SERVICES.find((entry) => entry.service === service)?.page
|
|
63
|
+
if (page === undefined) throw new Error(`Unknown Zen service ${String(service)}.`)
|
|
64
|
+
return `${docsBase(ref)}/${page}`
|
|
54
65
|
}
|
|
55
66
|
|
|
56
67
|
/** The models-list endpoint of one service. */
|
|
@@ -184,7 +195,7 @@ export async function fetchZenCatalogueSources(
|
|
|
184
195
|
): Promise<ZenCatalogueSources> {
|
|
185
196
|
const pageLimit = positive(options.limits?.pageBytes, MIB, 'limits.pageBytes')
|
|
186
197
|
const modelsDevLimit = positive(options.limits?.modelsDevBytes, 32 * MIB, 'limits.modelsDevBytes')
|
|
187
|
-
const
|
|
198
|
+
const ref = options.docsRef ?? ZEN_DOCS_REF
|
|
188
199
|
const failFast = new AbortController()
|
|
189
200
|
const scoped: FetchZenCatalogueOptions = {
|
|
190
201
|
...options,
|
|
@@ -199,8 +210,8 @@ export async function fetchZenCatalogueSources(
|
|
|
199
210
|
})
|
|
200
211
|
try {
|
|
201
212
|
const [zen, go, modelsDev, served] = await Promise.all([
|
|
202
|
-
read(readZenSource(
|
|
203
|
-
read(readZenSource(
|
|
213
|
+
read(readZenSource(zenDocsUrl('zen', ref), pageLimit, scoped)),
|
|
214
|
+
read(readZenSource(zenDocsUrl('go', ref), pageLimit, scoped)),
|
|
204
215
|
read(readZenSource(options.modelsDevUrl ?? ZEN_MODELS_DEV_URL, modelsDevLimit, scoped)),
|
|
205
216
|
read(fetchZenServedRosters(scoped)),
|
|
206
217
|
])
|
package/src/client.ts
CHANGED
|
@@ -32,15 +32,14 @@ import {
|
|
|
32
32
|
findZenCatalogueModel,
|
|
33
33
|
isUnroutedZenModel,
|
|
34
34
|
} from './catalogue/catalogue.js'
|
|
35
|
-
import { isDirectAnonymousZenId } from './catalogue/derive.js'
|
|
36
35
|
import { isExperimentalFreeZenModel } from './catalogue/experimental-free.js'
|
|
37
36
|
import {
|
|
38
|
-
EXPERIMENTAL_OPENCODE_USER_AGENT,
|
|
39
37
|
experimentalOpenCodeRequestId,
|
|
40
38
|
experimentalOpenCodeSessionId,
|
|
39
|
+
experimentalOpenCodeUserAgent,
|
|
41
40
|
} from './experimental-identity.js'
|
|
42
41
|
import type { ZenModel, ZenService } from './models.js'
|
|
43
|
-
import { createCallOptions } from './options.js'
|
|
42
|
+
import { createCallOptions, withZenFreeTierTools } from './options.js'
|
|
44
43
|
import {
|
|
45
44
|
createReplayState,
|
|
46
45
|
isGoDeepSeekV4ResponsesModel,
|
|
@@ -369,12 +368,10 @@ export class ZenProvider implements LLMProvider {
|
|
|
369
368
|
params.model || this.config.model || (this.anonymous ? 'space-bunny-free' : 'glm-5.3-flash')
|
|
370
369
|
const catalogue = this.runtimeCatalogue()
|
|
371
370
|
const info = findZenCatalogueModel(catalogue, this.service, model)
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
const experimentalAnonymous = isExperimentalFreeZenModel(this.service, info)
|
|
377
|
-
if (this.anonymous && !verifiedAnonymous && !experimentalAnonymous)
|
|
371
|
+
// Every zero-price Zen model is admitted anonymously; the free-tier
|
|
372
|
+
// gateway then decides per request. `supportsAnonymousAccess` still
|
|
373
|
+
// marks the one model whose keyless path was verified directly.
|
|
374
|
+
if (this.anonymous && !isExperimentalFreeZenModel(this.service, info))
|
|
378
375
|
throw new ProviderRequestError({
|
|
379
376
|
providerId: this.id,
|
|
380
377
|
kind: 'auth',
|
|
@@ -402,21 +399,30 @@ export class ZenProvider implements LLMProvider {
|
|
|
402
399
|
...(params.signal ? [params.signal] : []),
|
|
403
400
|
])
|
|
404
401
|
let reader: ReadableStreamDefaultReader<LanguageModelV3StreamPart> | undefined
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
402
|
+
// The free-tier gateway admits an anonymous request only when it is
|
|
403
|
+
// shaped as the OpenCode client shapes it: an OpenCode User-Agent, a
|
|
404
|
+
// descending `ses_` session id, and the four tool names the client
|
|
405
|
+
// always registers. A caller-owned base URL is not the gateway, so the
|
|
406
|
+
// identity stays off it.
|
|
407
|
+
const freeTierGate = this.anonymous && this.baseURL === ZEN_BASE_URL
|
|
408
|
+
const gateSession = freeTierGate
|
|
409
|
+
? experimentalOpenCodeSessionId(this.sessionId)
|
|
410
|
+
: this.sessionId
|
|
411
|
+
const gateHeaders = freeTierGate
|
|
412
|
+
? {
|
|
413
|
+
'User-Agent': experimentalOpenCodeUserAgent(),
|
|
414
|
+
'x-opencode-client': 'cli',
|
|
415
|
+
'x-opencode-project': 'global',
|
|
416
|
+
'x-opencode-session': gateSession,
|
|
417
|
+
'x-opencode-session-id': gateSession,
|
|
418
|
+
}
|
|
419
|
+
: undefined
|
|
414
420
|
const modelFetch: typeof fetch = (input, init) =>
|
|
415
421
|
this.requestFetch(
|
|
416
422
|
input,
|
|
417
423
|
init,
|
|
418
|
-
|
|
419
|
-
? { ...
|
|
424
|
+
gateHeaders
|
|
425
|
+
? { ...gateHeaders, 'x-opencode-request': experimentalOpenCodeRequestId() }
|
|
420
426
|
: undefined,
|
|
421
427
|
)
|
|
422
428
|
try {
|
|
@@ -432,6 +438,7 @@ export class ZenProvider implements LLMProvider {
|
|
|
432
438
|
detail: 'The message history contains content this model protocol cannot encode.',
|
|
433
439
|
})
|
|
434
440
|
}
|
|
441
|
+
options = withZenFreeTierTools(options, freeTierGate, protocol !== 'responses')
|
|
435
442
|
options.abortSignal = signal
|
|
436
443
|
const settings = {
|
|
437
444
|
apiKey: this.apiKey,
|
|
@@ -1,10 +1,73 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process'
|
|
1
2
|
import { createHash, randomBytes } from 'node:crypto'
|
|
2
3
|
|
|
3
4
|
const ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
|
|
4
5
|
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
/**
|
|
7
|
+
* The floor the free-tier gate enforces: `OpenCode 1.18.0 or newer is required
|
|
8
|
+
* to use the free tier`. Anything older answers HTTP 426 `UpgradeRequired`.
|
|
9
|
+
* This pin is only a fallback for a host with no installed OpenCode — the
|
|
10
|
+
* version actually advertised is resolved from the installed CLI when one can
|
|
11
|
+
* be found, so this constant is never the reason a request is refused.
|
|
12
|
+
*/
|
|
13
|
+
export const FALLBACK_OPENCODE_VERSION = '1.18.35'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The four tool names the free-tier gate requires in every anonymous request
|
|
17
|
+
* body. A request missing any one of them — or carrying an empty or absent
|
|
18
|
+
* `tools` — is refused with HTTP 403 `FreeTierError`. The names are the ones
|
|
19
|
+
* OpenCode itself always registers, so a host that already offers tools under
|
|
20
|
+
* these names needs nothing injected.
|
|
21
|
+
*/
|
|
22
|
+
export const ZEN_FREE_TIER_TOOL_NAMES = Object.freeze(['bash', 'glob', 'grep', 'read'] as const)
|
|
23
|
+
|
|
24
|
+
/** `OpenCode <version> (…)` as the installed CLI reports it. */
|
|
25
|
+
export function parseOpenCodeVersion(stdout: string): string | undefined {
|
|
26
|
+
const match = /(\d+\.\d+\.\d+)/.exec(stdout)
|
|
27
|
+
return match?.[1]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let resolvedUserAgent: string | undefined
|
|
31
|
+
|
|
32
|
+
function readInstalledOpenCodeVersion(): string | undefined {
|
|
33
|
+
// A provider must not spawn a process per request, so the lookup runs once
|
|
34
|
+
// per process and every failure — no binary, no permission, a version that
|
|
35
|
+
// does not parse — falls through to the pinned floor. The stdin is closed
|
|
36
|
+
// so a CLI that would prompt cannot hang the caller.
|
|
37
|
+
try {
|
|
38
|
+
const output = execFileSync('opencode', ['--version'], {
|
|
39
|
+
timeout: 2_000,
|
|
40
|
+
encoding: 'utf8',
|
|
41
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
42
|
+
})
|
|
43
|
+
return parseOpenCodeVersion(output)
|
|
44
|
+
} catch {
|
|
45
|
+
return undefined
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The User-Agent an anonymous Zen request advertises.
|
|
51
|
+
*
|
|
52
|
+
* Resolution order: `OPENCODE_UA` verbatim, then the installed OpenCode CLI's
|
|
53
|
+
* version, then {@link FALLBACK_OPENCODE_VERSION}. The free-tier gate reads the
|
|
54
|
+
* version out of this header and refuses anything below 1.18, so a host that
|
|
55
|
+
* has OpenCode installed stands in for the version it actually has.
|
|
56
|
+
*/
|
|
57
|
+
export function experimentalOpenCodeUserAgent(): string {
|
|
58
|
+
if (resolvedUserAgent !== undefined) return resolvedUserAgent
|
|
59
|
+
const override = process.env.OPENCODE_UA?.trim()
|
|
60
|
+
const version = override || readInstalledOpenCodeVersion() || FALLBACK_OPENCODE_VERSION
|
|
61
|
+
resolvedUserAgent = override
|
|
62
|
+
? override
|
|
63
|
+
: `opencode/${version} ai-sdk/provider-utils/4.0.23 runtime/bun/1.3.14`
|
|
64
|
+
return resolvedUserAgent
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Test seam: forget the memoized User-Agent so the next call re-resolves it. */
|
|
68
|
+
export function resetExperimentalOpenCodeUserAgent(): void {
|
|
69
|
+
resolvedUserAgent = undefined
|
|
70
|
+
}
|
|
8
71
|
|
|
9
72
|
function timeHex(value: bigint): string {
|
|
10
73
|
const time = Buffer.alloc(6)
|
|
@@ -15,10 +78,15 @@ function timeHex(value: bigint): string {
|
|
|
15
78
|
}
|
|
16
79
|
|
|
17
80
|
function base62(bytes: Uint8Array): string {
|
|
18
|
-
return Array.from(bytes, (byte) => ALPHABET[byte %
|
|
81
|
+
return Array.from(bytes, (byte) => ALPHABET[byte % 62]).join('')
|
|
19
82
|
}
|
|
20
83
|
|
|
21
|
-
/**
|
|
84
|
+
/**
|
|
85
|
+
* A session id in the exact descending shape OpenCode emits, which is the
|
|
86
|
+
* shape the free-tier gate parses: `ses_`, 12 lowercase hex digits carrying
|
|
87
|
+
* the inverted timestamp with its low 12 bits as `0xfff − counter`, then 14
|
|
88
|
+
* base62 characters. The counter is 1, well inside the gate's 0–63 window.
|
|
89
|
+
*/
|
|
22
90
|
export function experimentalOpenCodeSessionId(namzuSessionId: string): string {
|
|
23
91
|
const hash = createHash('sha256').update(namzuSessionId).digest()
|
|
24
92
|
const uuidTime = /^([0-9a-f]{8})-([0-9a-f]{4})-7[0-9a-f]{3}-/i.exec(namzuSessionId)
|
package/src/models.review.json
CHANGED
|
@@ -1,36 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"omissions": {
|
|
3
3
|
"go/deepseek-flash": "Served by the Go service and named by no route row on either page; models.dev carries no entry for it either, so neither a wire nor its limits can be derived.",
|
|
4
|
-
"go/glm-5": "Served by Go and no longer routed by the Go page, which routes GLM 5.2, 5.3 and 5.3 Flash. The Zen page still routes this id, and one service's route is not the other's, so none is taken from it.",
|
|
5
4
|
"go/glm-5.1": "Still served by Go, but the current Go page states neither a route nor a price for it. The /models answer states no wire, so the old bundled route is not carried without current evidence.",
|
|
6
|
-
"go/grok-4.5": "Served by Go and absent from the Go page, which routes Grok 4.6. models.dev states an npm package for it, and that field is deliberately not used as a route, so none is carried.",
|
|
7
|
-
"go/hy3-preview": "Served by Go and named by no route row on either page; models.dev carries no entry for it, so neither a wire nor its limits can be derived.",
|
|
8
|
-
"go/kimi-k2.5": "Served by Go and absent from the Go page, which routes Kimi K2.6, K2.7 Code and K3. The Zen page still routes this id, and one service's route is not the other's, so none is taken from it.",
|
|
9
|
-
"go/mimo-v2-omni": "Served by Go and absent from the Go page, which routes MiMo V2.5 and V2.5 Pro. models.dev states a name and limits for it and no wire, so none is carried.",
|
|
10
|
-
"go/mimo-v2-pro": "Served by Go and absent from the Go page, which routes MiMo V2.5 and V2.5 Pro. models.dev states a name and limits for it and no wire, so none is carried.",
|
|
11
|
-
"go/mimo-v2.6-flash": "Routed by the Go page since 2026-09-21, and models.dev carries no `opencode-go` entry for it yet, so its limits cannot be derived and it is not carried.",
|
|
12
|
-
"go/mimo-v2.6-pro": "Routed by the Go page since 2026-09-21, and models.dev carries no `opencode-go` entry for it yet, so its limits cannot be derived and it is not carried.",
|
|
13
5
|
"go/minimax-m2.5": "Superseded on Go by MiniMax M2.7 and M3, which are both carried. The page still lists it.",
|
|
14
6
|
"go/omen-alpha": "Served by Go and no longer routed by the Go page, which is why this release dropped it from the catalogue. No source states a wire for it any more, so a caller that needs it must name the protocol itself.",
|
|
15
|
-
"go/qwen3.5-plus": "Served by Go and absent from the Go page, which routes Qwen 3.7 Plus, 3.8 Flash and 3.8 Max. The Zen page routes this id on Messages, and one service's route is not the other's, so none is taken from it.",
|
|
16
7
|
"go/qwen3.6-plus": "Still served by Go, but the current Go page states neither a route nor a price for it. The /models answer states no wire, and the old bundled Messages route conflicts with the reference client's Chat route, so it is not carried by guessing.",
|
|
17
8
|
"go/qwen3.7-max": "Still served by Go, but the current Go page states neither a route nor a price for it. The /models answer states no wire, so the old bundled route is not carried without current evidence.",
|
|
18
|
-
"zen/claude-
|
|
19
|
-
"zen/
|
|
9
|
+
"zen/claude-opus-5-5-backup": "Served by the Zen /models answer as a failover twin of a routed model and named by no route row on either page. A backup id carries no route of its own, so no wire can be derived and a caller that needs it must name the protocol explicitly.",
|
|
10
|
+
"zen/gemini-4-argon": "Routed by the Zen page, which states neither a price row nor a free-list entry for it. A price that cannot be read is never defaulted to zero, so it is not carried — least of all anonymously.",
|
|
20
11
|
"zen/glm-5": "Superseded by GLM 5.1, 5.2 and 5.3, which are all carried. The page still lists it.",
|
|
21
12
|
"zen/gpt-5-codex": "Superseded Codex generation; gpt-5.3-codex and gpt-5.3-codex-spark are carried.",
|
|
22
13
|
"zen/gpt-5.1-codex": "Superseded Codex generation; gpt-5.3-codex and gpt-5.3-codex-spark are carried.",
|
|
23
14
|
"zen/gpt-5.1-codex-max": "Superseded Codex generation; gpt-5.3-codex and gpt-5.3-codex-spark are carried.",
|
|
24
15
|
"zen/gpt-5.1-codex-mini": "Superseded Codex generation; gpt-5.3-codex and gpt-5.3-codex-spark are carried.",
|
|
25
16
|
"zen/gpt-5.2-codex": "Superseded Codex generation; gpt-5.3-codex and gpt-5.3-codex-spark are carried.",
|
|
26
|
-
"zen/grok-4.7": "Routed by the Zen page since 2026-09-21, and models.dev carries no `opencode` entry for it yet, so its limits cannot be derived and it is not carried.",
|
|
27
17
|
"zen/jev-1.13": "Routed by the Zen page on an endpoint whose AI SDK package column carries a dash, the marker that page uses for a cell with no value: no wire is stated for it, so none can be carried. The service serves the id, and models.dev carries a name and limits for it and no package either.",
|
|
28
18
|
"zen/jev-1.13-free": "Routed by the Zen page on an endpoint whose AI SDK package column carries a dash, the marker that page uses for a cell with no value: no wire is stated for it, so none can be carried. The service serves the id and the page's free-model list names it, and models.dev carries a name and limits for it and no package either.",
|
|
29
19
|
"zen/kimi-k2.5": "Superseded by Kimi K2.6, K2.7 Code and K3, which are all carried.",
|
|
30
|
-
"zen/mimo-v2.6-flash-free": "Routed by the Zen page since 2026-09-21, and models.dev carries no `opencode` entry for it yet, so its limits cannot be derived and it is not carried.",
|
|
31
20
|
"zen/minimax-m2.5": "Superseded by MiniMax M2.7 and M3, which are both carried.",
|
|
21
|
+
"zen/mistral-large-4": "Routed by the Zen page, which states neither a price row nor a free-list entry for it. A price that cannot be read is never defaulted to zero, so it is not carried.",
|
|
32
22
|
"zen/muse-spark-1.2-contributor-free": "Served by Zen under an id no page routes: the Zen page's free-model list names the 1.3 contributor model, and Go's 1.2 contributor row is a different id on the other service. models.dev states an npm package for it, and that field is deliberately not used as a route, so none is carried.",
|
|
33
|
-
"zen/qwen3.7-max": "models.dev carries no entry for it
|
|
34
|
-
"zen/qwen3.7-plus": "models.dev carries no entry for it
|
|
23
|
+
"zen/qwen3.7-max": "Routed by the Zen page, but models.dev carries no entry for it and the Zen service's /models answer does not serve it, so nothing states that it can be called and it is not carried. It is carried with conservative limits as soon as the service serves it.",
|
|
24
|
+
"zen/qwen3.7-plus": "Routed by the Zen page, but models.dev carries no entry for it and the Zen service's /models answer does not serve it, so nothing states that it can be called and it is not carried. It is carried with conservative limits as soon as the service serves it.",
|
|
25
|
+
"zen/test": "Served by the Zen /models answer and named by no route row on either page, and its id reads as an endpoint fixture rather than a model. No wire can be derived, and nothing about it should ever be carried or admitted anonymously."
|
|
35
26
|
}
|
|
36
27
|
}
|
package/src/models.ts
CHANGED
|
@@ -22,6 +22,8 @@ export interface ZenModel {
|
|
|
22
22
|
readonly supportsStreaming: boolean
|
|
23
23
|
/** True only for verified direct-anonymous Zen models; zero price does not grant access. */
|
|
24
24
|
readonly supportsAnonymousAccess?: boolean
|
|
25
|
+
/** False: the limits are conservative placeholders models.dev has not published. */
|
|
26
|
+
readonly limitsVerified?: false
|
|
25
27
|
/** Exact advertised selectable effort levels; empty means no effort selector. */
|
|
26
28
|
readonly effortLevels?: readonly ReasoningEffort[]
|
|
27
29
|
}
|
|
@@ -35,7 +37,7 @@ export interface ZenModel {
|
|
|
35
37
|
* derived is an edit to src/catalogue/derive.ts. Both survive regeneration; a
|
|
36
38
|
* hand edit here does not.
|
|
37
39
|
*
|
|
38
|
-
* Refreshed: 2026-
|
|
40
|
+
* Refreshed: 2026-10-13
|
|
39
41
|
*
|
|
40
42
|
* This is the BUNDLED snapshot: what the driver knows with no network. A host
|
|
41
43
|
* can derive a fresher roster at run time from the same sources, by the same
|
|
@@ -116,6 +118,19 @@ const ZEN_MODELS = freezeModels([
|
|
|
116
118
|
supportsStreaming: true,
|
|
117
119
|
effortLevels: ['none', 'low', 'medium', 'high', 'xhigh', 'max'],
|
|
118
120
|
},
|
|
121
|
+
{
|
|
122
|
+
id: 'gpt-6.1-sol',
|
|
123
|
+
name: 'GPT 6.1 Sol',
|
|
124
|
+
protocol: 'responses',
|
|
125
|
+
contextWindow: 1050000,
|
|
126
|
+
maxOutputTokens: 128000,
|
|
127
|
+
inputModalities: ['text', 'image', 'document'],
|
|
128
|
+
inputPrice: 2.0,
|
|
129
|
+
outputPrice: 10.0,
|
|
130
|
+
supportsToolUse: true,
|
|
131
|
+
supportsStreaming: true,
|
|
132
|
+
effortLevels: ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
133
|
+
},
|
|
119
134
|
{
|
|
120
135
|
id: 'gpt-6-luna',
|
|
121
136
|
name: 'GPT 6 Luna',
|
|
@@ -428,6 +443,19 @@ const ZEN_MODELS = freezeModels([
|
|
|
428
443
|
supportsStreaming: true,
|
|
429
444
|
effortLevels: ['low', 'medium', 'high'],
|
|
430
445
|
},
|
|
446
|
+
{
|
|
447
|
+
id: 'claude-sonnet-5-5',
|
|
448
|
+
name: 'Claude Sonnet 5.5',
|
|
449
|
+
protocol: 'messages',
|
|
450
|
+
contextWindow: 1000000,
|
|
451
|
+
maxOutputTokens: 128000,
|
|
452
|
+
inputModalities: ['text', 'image', 'document'],
|
|
453
|
+
inputPrice: 2.0,
|
|
454
|
+
outputPrice: 10.0,
|
|
455
|
+
supportsToolUse: true,
|
|
456
|
+
supportsStreaming: true,
|
|
457
|
+
effortLevels: ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
458
|
+
},
|
|
431
459
|
{
|
|
432
460
|
id: 'claude-sonnet-5',
|
|
433
461
|
name: 'Claude Sonnet 5',
|
|
@@ -467,6 +495,19 @@ const ZEN_MODELS = freezeModels([
|
|
|
467
495
|
supportsStreaming: true,
|
|
468
496
|
effortLevels: [],
|
|
469
497
|
},
|
|
498
|
+
{
|
|
499
|
+
id: 'claude-haiku-5-5',
|
|
500
|
+
name: 'Claude Haiku 5.5',
|
|
501
|
+
protocol: 'messages',
|
|
502
|
+
contextWindow: 1000000,
|
|
503
|
+
maxOutputTokens: 128000,
|
|
504
|
+
inputModalities: ['text', 'image', 'document'],
|
|
505
|
+
inputPrice: 0.1,
|
|
506
|
+
outputPrice: 0.5,
|
|
507
|
+
supportsToolUse: true,
|
|
508
|
+
supportsStreaming: true,
|
|
509
|
+
effortLevels: ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
510
|
+
},
|
|
470
511
|
{
|
|
471
512
|
id: 'claude-haiku-4-5',
|
|
472
513
|
name: 'Claude Haiku 4.5',
|
|
@@ -571,6 +612,19 @@ const ZEN_MODELS = freezeModels([
|
|
|
571
612
|
supportsStreaming: true,
|
|
572
613
|
effortLevels: ['minimal', 'low', 'medium', 'high'],
|
|
573
614
|
},
|
|
615
|
+
{
|
|
616
|
+
id: 'grok-4.7',
|
|
617
|
+
name: 'Grok 4.7',
|
|
618
|
+
protocol: 'responses',
|
|
619
|
+
contextWindow: 500000,
|
|
620
|
+
maxOutputTokens: 500000,
|
|
621
|
+
inputModalities: ['text', 'image', 'document'],
|
|
622
|
+
inputPrice: 2.0,
|
|
623
|
+
outputPrice: 6.0,
|
|
624
|
+
supportsToolUse: true,
|
|
625
|
+
supportsStreaming: true,
|
|
626
|
+
effortLevels: ['low', 'medium', 'high', 'xhigh'],
|
|
627
|
+
},
|
|
574
628
|
{
|
|
575
629
|
id: 'grok-4.6',
|
|
576
630
|
name: 'Grok 4.6',
|
|
@@ -884,6 +938,19 @@ const ZEN_MODELS = freezeModels([
|
|
|
884
938
|
supportsStreaming: true,
|
|
885
939
|
effortLevels: ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
886
940
|
},
|
|
941
|
+
{
|
|
942
|
+
id: 'cat-jam-free',
|
|
943
|
+
name: 'Cat Jam Free',
|
|
944
|
+
protocol: 'chat',
|
|
945
|
+
contextWindow: 262144,
|
|
946
|
+
maxOutputTokens: 131072,
|
|
947
|
+
inputModalities: ['text'],
|
|
948
|
+
inputPrice: 0,
|
|
949
|
+
outputPrice: 0,
|
|
950
|
+
supportsToolUse: true,
|
|
951
|
+
supportsStreaming: true,
|
|
952
|
+
effortLevels: ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
953
|
+
},
|
|
887
954
|
{
|
|
888
955
|
id: 'longcat-2.5-preview-free',
|
|
889
956
|
name: 'LongCat 2.5 Preview Free',
|
|
@@ -897,6 +964,45 @@ const ZEN_MODELS = freezeModels([
|
|
|
897
964
|
supportsStreaming: true,
|
|
898
965
|
effortLevels: [],
|
|
899
966
|
},
|
|
967
|
+
{
|
|
968
|
+
id: 'step-5-preview-free',
|
|
969
|
+
name: 'Step 5 Preview Free',
|
|
970
|
+
protocol: 'chat',
|
|
971
|
+
contextWindow: 1000000,
|
|
972
|
+
maxOutputTokens: 65536,
|
|
973
|
+
inputModalities: ['text', 'image'],
|
|
974
|
+
inputPrice: 0,
|
|
975
|
+
outputPrice: 0,
|
|
976
|
+
supportsToolUse: true,
|
|
977
|
+
supportsStreaming: true,
|
|
978
|
+
effortLevels: ['low', 'medium', 'high'],
|
|
979
|
+
},
|
|
980
|
+
{
|
|
981
|
+
id: 'exo-free',
|
|
982
|
+
name: 'Exo Free',
|
|
983
|
+
protocol: 'chat',
|
|
984
|
+
contextWindow: 1048576,
|
|
985
|
+
maxOutputTokens: 131072,
|
|
986
|
+
inputModalities: ['text', 'image'],
|
|
987
|
+
inputPrice: 0,
|
|
988
|
+
outputPrice: 0,
|
|
989
|
+
supportsToolUse: true,
|
|
990
|
+
supportsStreaming: true,
|
|
991
|
+
effortLevels: ['high'],
|
|
992
|
+
},
|
|
993
|
+
{
|
|
994
|
+
id: 'mimo-v2.6-flash-free',
|
|
995
|
+
name: 'MiMo-V2.6-Flash Free',
|
|
996
|
+
protocol: 'chat',
|
|
997
|
+
contextWindow: 200000,
|
|
998
|
+
maxOutputTokens: 32000,
|
|
999
|
+
inputModalities: ['text', 'image'],
|
|
1000
|
+
inputPrice: 0,
|
|
1001
|
+
outputPrice: 0,
|
|
1002
|
+
supportsToolUse: true,
|
|
1003
|
+
supportsStreaming: true,
|
|
1004
|
+
effortLevels: [],
|
|
1005
|
+
},
|
|
900
1006
|
{
|
|
901
1007
|
id: 'mimo-v2.5-free',
|
|
902
1008
|
name: 'MiMo-V2.5 Free',
|
|
@@ -910,6 +1016,19 @@ const ZEN_MODELS = freezeModels([
|
|
|
910
1016
|
supportsStreaming: true,
|
|
911
1017
|
effortLevels: [],
|
|
912
1018
|
},
|
|
1019
|
+
{
|
|
1020
|
+
id: 'ling-3.1-flash-free',
|
|
1021
|
+
name: 'Ling 3.1 Flash Free',
|
|
1022
|
+
protocol: 'chat',
|
|
1023
|
+
contextWindow: 262144,
|
|
1024
|
+
maxOutputTokens: 32768,
|
|
1025
|
+
inputModalities: ['text'],
|
|
1026
|
+
inputPrice: 0,
|
|
1027
|
+
outputPrice: 0,
|
|
1028
|
+
supportsToolUse: true,
|
|
1029
|
+
supportsStreaming: true,
|
|
1030
|
+
effortLevels: [],
|
|
1031
|
+
},
|
|
913
1032
|
{
|
|
914
1033
|
id: 'ling-3.0-flash-fin-free',
|
|
915
1034
|
name: 'Ling 3.0 Flash Fin Free',
|
|
@@ -1017,6 +1136,19 @@ const GO_MODELS = freezeModels([
|
|
|
1017
1136
|
supportsStreaming: true,
|
|
1018
1137
|
effortLevels: ['none', 'low', 'medium', 'high', 'xhigh', 'max'],
|
|
1019
1138
|
},
|
|
1139
|
+
{
|
|
1140
|
+
id: 'claude-haiku-5-5',
|
|
1141
|
+
name: 'Claude Haiku 5.5',
|
|
1142
|
+
protocol: 'messages',
|
|
1143
|
+
contextWindow: 1000000,
|
|
1144
|
+
maxOutputTokens: 128000,
|
|
1145
|
+
inputModalities: ['text', 'image', 'document'],
|
|
1146
|
+
inputPrice: 0.1,
|
|
1147
|
+
outputPrice: 0.5,
|
|
1148
|
+
supportsToolUse: true,
|
|
1149
|
+
supportsStreaming: true,
|
|
1150
|
+
effortLevels: ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
1151
|
+
},
|
|
1020
1152
|
{
|
|
1021
1153
|
id: 'glm-5.3-flash',
|
|
1022
1154
|
name: 'GLM-5.3-Flash',
|
|
@@ -1108,19 +1240,6 @@ const GO_MODELS = freezeModels([
|
|
|
1108
1240
|
supportsStreaming: true,
|
|
1109
1241
|
effortLevels: [],
|
|
1110
1242
|
},
|
|
1111
|
-
{
|
|
1112
|
-
id: 'longcat-2.5-preview-free',
|
|
1113
|
-
name: 'LongCat 2.5 Preview Free',
|
|
1114
|
-
protocol: 'chat',
|
|
1115
|
-
contextWindow: 1000000,
|
|
1116
|
-
maxOutputTokens: 131072,
|
|
1117
|
-
inputModalities: ['text', 'image'],
|
|
1118
|
-
inputPrice: 0,
|
|
1119
|
-
outputPrice: 0,
|
|
1120
|
-
supportsToolUse: true,
|
|
1121
|
-
supportsStreaming: true,
|
|
1122
|
-
effortLevels: [],
|
|
1123
|
-
},
|
|
1124
1243
|
{
|
|
1125
1244
|
id: 'deepseek-v4.1-flash',
|
|
1126
1245
|
name: 'DeepSeek V4.1 Flash',
|
|
@@ -1173,6 +1292,32 @@ const GO_MODELS = freezeModels([
|
|
|
1173
1292
|
supportsStreaming: true,
|
|
1174
1293
|
effortLevels: ['low', 'high', 'max'],
|
|
1175
1294
|
},
|
|
1295
|
+
{
|
|
1296
|
+
id: 'mimo-v2.6-flash',
|
|
1297
|
+
name: 'MiMo-V2.6-Flash',
|
|
1298
|
+
protocol: 'chat',
|
|
1299
|
+
contextWindow: 1048576,
|
|
1300
|
+
maxOutputTokens: 131072,
|
|
1301
|
+
inputModalities: ['text', 'image'],
|
|
1302
|
+
inputPrice: 0.14,
|
|
1303
|
+
outputPrice: 0.28,
|
|
1304
|
+
supportsToolUse: true,
|
|
1305
|
+
supportsStreaming: true,
|
|
1306
|
+
effortLevels: [],
|
|
1307
|
+
},
|
|
1308
|
+
{
|
|
1309
|
+
id: 'mimo-v2.6-pro',
|
|
1310
|
+
name: 'MiMo-V2.6-Pro',
|
|
1311
|
+
protocol: 'chat',
|
|
1312
|
+
contextWindow: 1048576,
|
|
1313
|
+
maxOutputTokens: 131072,
|
|
1314
|
+
inputModalities: ['text', 'image'],
|
|
1315
|
+
inputPrice: 0.435,
|
|
1316
|
+
outputPrice: 0.87,
|
|
1317
|
+
supportsToolUse: true,
|
|
1318
|
+
supportsStreaming: true,
|
|
1319
|
+
effortLevels: [],
|
|
1320
|
+
},
|
|
1176
1321
|
{
|
|
1177
1322
|
id: 'mimo-v2.5',
|
|
1178
1323
|
name: 'MiMo-V2.5',
|
|
@@ -1317,12 +1462,51 @@ const GO_MODELS = freezeModels([
|
|
|
1317
1462
|
effortLevels: ['none', 'low', 'high'],
|
|
1318
1463
|
},
|
|
1319
1464
|
{
|
|
1320
|
-
id: 'space-bunny
|
|
1321
|
-
name: 'Space Bunny
|
|
1465
|
+
id: 'space-bunny',
|
|
1466
|
+
name: 'Space Bunny',
|
|
1322
1467
|
protocol: 'chat',
|
|
1323
1468
|
contextWindow: 1048576,
|
|
1324
1469
|
maxOutputTokens: 524288,
|
|
1325
1470
|
inputModalities: ['text', 'image'],
|
|
1471
|
+
inputPrice: 0.15,
|
|
1472
|
+
outputPrice: 0.6,
|
|
1473
|
+
supportsToolUse: true,
|
|
1474
|
+
supportsStreaming: true,
|
|
1475
|
+
effortLevels: ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
1476
|
+
},
|
|
1477
|
+
{
|
|
1478
|
+
id: 'longcat-2.5-preview-free',
|
|
1479
|
+
name: 'LongCat 2.5 Preview Free',
|
|
1480
|
+
protocol: 'chat',
|
|
1481
|
+
contextWindow: 1000000,
|
|
1482
|
+
maxOutputTokens: 131072,
|
|
1483
|
+
inputModalities: ['text', 'image'],
|
|
1484
|
+
inputPrice: 0,
|
|
1485
|
+
outputPrice: 0,
|
|
1486
|
+
supportsToolUse: true,
|
|
1487
|
+
supportsStreaming: true,
|
|
1488
|
+
effortLevels: [],
|
|
1489
|
+
},
|
|
1490
|
+
{
|
|
1491
|
+
id: 'step-5-preview-free',
|
|
1492
|
+
name: 'Step 5 Preview Free',
|
|
1493
|
+
protocol: 'chat',
|
|
1494
|
+
contextWindow: 1000000,
|
|
1495
|
+
maxOutputTokens: 65536,
|
|
1496
|
+
inputModalities: ['text', 'image'],
|
|
1497
|
+
inputPrice: 0,
|
|
1498
|
+
outputPrice: 0,
|
|
1499
|
+
supportsToolUse: true,
|
|
1500
|
+
supportsStreaming: true,
|
|
1501
|
+
effortLevels: ['low', 'medium', 'high'],
|
|
1502
|
+
},
|
|
1503
|
+
{
|
|
1504
|
+
id: 'cat-jam-free',
|
|
1505
|
+
name: 'Cat Jam Free',
|
|
1506
|
+
protocol: 'chat',
|
|
1507
|
+
contextWindow: 262144,
|
|
1508
|
+
maxOutputTokens: 131072,
|
|
1509
|
+
inputModalities: ['text'],
|
|
1326
1510
|
inputPrice: 0,
|
|
1327
1511
|
outputPrice: 0,
|
|
1328
1512
|
supportsToolUse: true,
|
|
@@ -1338,37 +1522,28 @@ const GO_MODELS = freezeModels([
|
|
|
1338
1522
|
*/
|
|
1339
1523
|
export const ZEN_OMITTED_MODELS: readonly string[] = Object.freeze([
|
|
1340
1524
|
'go/deepseek-flash',
|
|
1341
|
-
'go/glm-5',
|
|
1342
1525
|
'go/glm-5.1',
|
|
1343
|
-
'go/grok-4.5',
|
|
1344
|
-
'go/hy3-preview',
|
|
1345
|
-
'go/kimi-k2.5',
|
|
1346
|
-
'go/mimo-v2-omni',
|
|
1347
|
-
'go/mimo-v2-pro',
|
|
1348
|
-
'go/mimo-v2.6-flash',
|
|
1349
|
-
'go/mimo-v2.6-pro',
|
|
1350
1526
|
'go/minimax-m2.5',
|
|
1351
1527
|
'go/omen-alpha',
|
|
1352
|
-
'go/qwen3.5-plus',
|
|
1353
1528
|
'go/qwen3.6-plus',
|
|
1354
1529
|
'go/qwen3.7-max',
|
|
1355
|
-
'zen/claude-
|
|
1356
|
-
'zen/
|
|
1530
|
+
'zen/claude-opus-5-5-backup',
|
|
1531
|
+
'zen/gemini-4-argon',
|
|
1357
1532
|
'zen/glm-5',
|
|
1358
1533
|
'zen/gpt-5-codex',
|
|
1359
1534
|
'zen/gpt-5.1-codex',
|
|
1360
1535
|
'zen/gpt-5.1-codex-max',
|
|
1361
1536
|
'zen/gpt-5.1-codex-mini',
|
|
1362
1537
|
'zen/gpt-5.2-codex',
|
|
1363
|
-
'zen/grok-4.7',
|
|
1364
1538
|
'zen/jev-1.13',
|
|
1365
1539
|
'zen/jev-1.13-free',
|
|
1366
1540
|
'zen/kimi-k2.5',
|
|
1367
|
-
'zen/mimo-v2.6-flash-free',
|
|
1368
1541
|
'zen/minimax-m2.5',
|
|
1542
|
+
'zen/mistral-large-4',
|
|
1369
1543
|
'zen/muse-spark-1.2-contributor-free',
|
|
1370
1544
|
'zen/qwen3.7-max',
|
|
1371
1545
|
'zen/qwen3.7-plus',
|
|
1546
|
+
'zen/test',
|
|
1372
1547
|
])
|
|
1373
1548
|
|
|
1374
1549
|
/** Supported metadata; actual account availability is established by live discovery. */
|