@namzu/zen 3.0.0 → 4.0.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +38 -15
  3. package/dist/catalogue/catalogue.d.ts.map +1 -1
  4. package/dist/catalogue/catalogue.js +6 -0
  5. package/dist/catalogue/catalogue.js.map +1 -1
  6. package/dist/catalogue/derive.d.ts +15 -1
  7. package/dist/catalogue/derive.d.ts.map +1 -1
  8. package/dist/catalogue/derive.js +73 -10
  9. package/dist/catalogue/derive.js.map +1 -1
  10. package/dist/catalogue/experimental-free.d.ts +8 -0
  11. package/dist/catalogue/experimental-free.d.ts.map +1 -0
  12. package/dist/catalogue/experimental-free.js +9 -0
  13. package/dist/catalogue/experimental-free.js.map +1 -0
  14. package/dist/catalogue/fetch.d.ts +2 -0
  15. package/dist/catalogue/fetch.d.ts.map +1 -1
  16. package/dist/catalogue/fetch.js +14 -5
  17. package/dist/catalogue/fetch.js.map +1 -1
  18. package/dist/catalogue/index.d.ts +1 -0
  19. package/dist/catalogue/index.d.ts.map +1 -1
  20. package/dist/catalogue/index.js +1 -0
  21. package/dist/catalogue/index.js.map +1 -1
  22. package/dist/client.d.ts.map +1 -1
  23. package/dist/client.js +40 -13
  24. package/dist/client.js.map +1 -1
  25. package/dist/experimental-identity.d.ts +39 -0
  26. package/dist/experimental-identity.d.ts.map +1 -0
  27. package/dist/experimental-identity.js +93 -0
  28. package/dist/experimental-identity.js.map +1 -0
  29. package/dist/models.d.ts +2 -0
  30. package/dist/models.d.ts.map +1 -1
  31. package/dist/models.js +202 -29
  32. package/dist/models.js.map +1 -1
  33. package/dist/options.d.ts +14 -0
  34. package/dist/options.d.ts.map +1 -1
  35. package/dist/options.js +93 -3
  36. package/dist/options.js.map +1 -1
  37. package/dist/prompt.d.ts.map +1 -1
  38. package/dist/prompt.js +50 -1
  39. package/dist/prompt.js.map +1 -1
  40. package/dist/types.d.ts +3 -3
  41. package/dist/types.d.ts.map +1 -1
  42. package/package.json +2 -2
  43. package/src/catalogue/catalogue.ts +6 -0
  44. package/src/catalogue/derive.ts +80 -9
  45. package/src/catalogue/experimental-free.ts +15 -0
  46. package/src/catalogue/fetch.ts +16 -5
  47. package/src/catalogue/index.ts +1 -0
  48. package/src/client.ts +52 -18
  49. package/src/experimental-identity.ts +102 -0
  50. package/src/models.review.json +6 -15
  51. package/src/models.ts +204 -29
  52. package/src/options.ts +103 -3
  53. package/src/prompt.ts +56 -1
  54. package/src/types.ts +3 -3
package/src/client.ts CHANGED
@@ -32,9 +32,14 @@ import {
32
32
  findZenCatalogueModel,
33
33
  isUnroutedZenModel,
34
34
  } from './catalogue/catalogue.js'
35
- import { isDirectAnonymousZenId } from './catalogue/derive.js'
35
+ import { isExperimentalFreeZenModel } from './catalogue/experimental-free.js'
36
+ import {
37
+ experimentalOpenCodeRequestId,
38
+ experimentalOpenCodeSessionId,
39
+ experimentalOpenCodeUserAgent,
40
+ } from './experimental-identity.js'
36
41
  import type { ZenModel, ZenService } from './models.js'
37
- import { createCallOptions } from './options.js'
42
+ import { createCallOptions, withZenFreeTierTools } from './options.js'
38
43
  import {
39
44
  createReplayState,
40
45
  isGoDeepSeekV4ResponsesModel,
@@ -294,7 +299,11 @@ export class ZenProvider implements LLMProvider {
294
299
  readonly sessionId: string
295
300
  private readonly baseURL: string
296
301
  private readonly timeout: number
297
- private readonly requestFetch: typeof fetch
302
+ private readonly requestFetch: (
303
+ input: Parameters<typeof fetch>[0],
304
+ init?: RequestInit,
305
+ overrides?: Readonly<Record<string, string>>,
306
+ ) => ReturnType<typeof fetch>
298
307
  private readonly apiKey: string
299
308
  private readonly anonymous: boolean
300
309
 
@@ -345,10 +354,11 @@ export class ZenProvider implements LLMProvider {
345
354
  this.timeout = config.timeout ?? 120_000
346
355
  if (!Number.isSafeInteger(this.timeout) || this.timeout <= 0)
347
356
  throw new Error('timeout must be a positive integer in milliseconds.')
348
- this.requestFetch = (input, init) => {
357
+ this.requestFetch = (input, init, overrides) => {
349
358
  const headers = new Headers(init?.headers)
350
359
  for (const [key, value] of Object.entries(attributionHeaders())) headers.set(key, value)
351
360
  headers.set('x-opencode-session', this.sessionId)
361
+ if (overrides) for (const [key, value] of Object.entries(overrides)) headers.set(key, value)
352
362
  return fetch(input, { ...init, headers, redirect: 'error' })
353
363
  }
354
364
  }
@@ -358,15 +368,15 @@ export class ZenProvider implements LLMProvider {
358
368
  params.model || this.config.model || (this.anonymous ? 'space-bunny-free' : 'glm-5.3-flash')
359
369
  const catalogue = this.runtimeCatalogue()
360
370
  const info = findZenCatalogueModel(catalogue, this.service, model)
361
- if (
362
- this.anonymous &&
363
- (!isDirectAnonymousZenId(model) || info?.supportsAnonymousAccess !== true)
364
- )
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))
365
375
  throw new ProviderRequestError({
366
376
  providerId: this.id,
367
377
  kind: 'auth',
368
378
  detail:
369
- 'Anonymous Zen access requires a supported free model. Configure an API key for other models.',
379
+ 'Anonymous Zen access requires a listed free model with a known route. Configure an API key for other models.',
370
380
  })
371
381
  const protocol = this.config.protocol ?? info?.protocol
372
382
  if (!protocol)
@@ -389,6 +399,32 @@ export class ZenProvider implements LLMProvider {
389
399
  ...(params.signal ? [params.signal] : []),
390
400
  ])
391
401
  let reader: ReadableStreamDefaultReader<LanguageModelV3StreamPart> | undefined
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
420
+ const modelFetch: typeof fetch = (input, init) =>
421
+ this.requestFetch(
422
+ input,
423
+ init,
424
+ gateHeaders
425
+ ? { ...gateHeaders, 'x-opencode-request': experimentalOpenCodeRequestId() }
426
+ : undefined,
427
+ )
392
428
  try {
393
429
  signal.throwIfAborted()
394
430
  let options: ReturnType<typeof createCallOptions>
@@ -402,6 +438,7 @@ export class ZenProvider implements LLMProvider {
402
438
  detail: 'The message history contains content this model protocol cannot encode.',
403
439
  })
404
440
  }
441
+ options = withZenFreeTierTools(options, freeTierGate, protocol !== 'responses')
405
442
  options.abortSignal = signal
406
443
  const settings = {
407
444
  apiKey: this.apiKey,
@@ -418,21 +455,21 @@ export class ZenProvider implements LLMProvider {
418
455
  body.generationConfig ??= {}
419
456
  delete body.generationConfig.responseSchema
420
457
  body.generationConfig.responseJsonSchema = params.responseFormat.json_schema.schema
421
- return this.requestFetch(input, {
458
+ return modelFetch(input, {
422
459
  ...init,
423
460
  body: JSON.stringify(body),
424
461
  })
425
462
  }
426
463
  if (isMuseSparkResponsesModel(model, protocol))
427
- return this.requestFetch(input, withoutMuseSparkEncryptedReasoning(init))
464
+ return modelFetch(input, withoutMuseSparkEncryptedReasoning(init))
428
465
  if (isGoDeepSeekV4ChatModel(this.service, model, protocol))
429
- return this.requestFetch(input, withGoDeepSeekV4ChatHistory(init, model))
466
+ return modelFetch(input, withGoDeepSeekV4ChatHistory(init, model))
430
467
  if (isGoDeepSeekV4ResponsesModel(this.service, model, protocol))
431
- return this.requestFetch(
468
+ return modelFetch(
432
469
  input,
433
470
  withGoDeepSeekV4ResponsesHistory(init, model, options.prompt),
434
471
  ).then(withGoDeepSeekReasoningTextEvents)
435
- return this.requestFetch(input, init)
472
+ return modelFetch(input, init)
436
473
  }) as typeof fetch,
437
474
  }
438
475
  let native: LanguageModelV3
@@ -750,10 +787,7 @@ export class ZenProvider implements LLMProvider {
750
787
  seen.add(item.id)
751
788
  const model = findZenCatalogueModel(catalogue, this.service, item.id)
752
789
  if (model) {
753
- if (
754
- !this.anonymous ||
755
- (isDirectAnonymousZenId(item.id) && model.supportsAnonymousAccess === true)
756
- )
790
+ if (!this.anonymous || isExperimentalFreeZenModel(this.service, model))
757
791
  result.push({ ...model })
758
792
  continue
759
793
  }
@@ -0,0 +1,102 @@
1
+ import { execFileSync } from 'node:child_process'
2
+ import { createHash, randomBytes } from 'node:crypto'
3
+
4
+ const ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
5
+
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
+ }
71
+
72
+ function timeHex(value: bigint): string {
73
+ const time = Buffer.alloc(6)
74
+ for (let index = 0; index < time.length; index++) {
75
+ time[index] = Number((value >> BigInt(40 - index * 8)) & 0xffn)
76
+ }
77
+ return time.toString('hex')
78
+ }
79
+
80
+ function base62(bytes: Uint8Array): string {
81
+ return Array.from(bytes, (byte) => ALPHABET[byte % 62]).join('')
82
+ }
83
+
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
+ */
90
+ export function experimentalOpenCodeSessionId(namzuSessionId: string): string {
91
+ const hash = createHash('sha256').update(namzuSessionId).digest()
92
+ const uuidTime = /^([0-9a-f]{8})-([0-9a-f]{4})-7[0-9a-f]{3}-/i.exec(namzuSessionId)
93
+ const milliseconds = uuidTime
94
+ ? BigInt(`0x${uuidTime[1]}${uuidTime[2]}`)
95
+ : BigInt(`0x${hash.subarray(0, 6).toString('hex')}`)
96
+ return `ses_${timeHex(~(milliseconds * 0x1000n + 1n))}${base62(hash.subarray(6, 20))}`
97
+ }
98
+
99
+ /** One OpenCode-shaped message id per outbound model request. */
100
+ export function experimentalOpenCodeRequestId(): string {
101
+ return `msg_${timeHex(BigInt(Date.now()) * 0x1000n + 1n)}${base62(randomBytes(14))}`
102
+ }
@@ -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-sonnet-4": "Served by Zen and named by no route row on either page: the Zen page routes Claude Sonnet 4.5 and 4.6. models.dev states an npm package for it, and that field is deliberately not used as a route, so none is carried.",
19
- "zen/deepseek-v4-flash-free": "Served by Zen and absent from the Zen page, which routes DeepSeek V4 Flash. models.dev states a name and limits for this free id and no wire, so none is carried.",
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, so its context window, limits and tool support cannot be derived and are never invented.",
34
- "zen/qwen3.7-plus": "models.dev carries no entry for it, so its context window, limits and tool support cannot be derived and are never invented."
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-09-28
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-free',
1321
- name: 'Space Bunny Free',
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-sonnet-4',
1356
- 'zen/deepseek-v4-flash-free',
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. */