@namzu/zen 3.1.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 (48) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +33 -14
  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 +5 -0
  11. package/dist/catalogue/experimental-free.d.ts.map +1 -1
  12. package/dist/catalogue/experimental-free.js +4 -19
  13. package/dist/catalogue/experimental-free.js.map +1 -1
  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/client.d.ts.map +1 -1
  19. package/dist/client.js +22 -13
  20. package/dist/client.js.map +1 -1
  21. package/dist/experimental-identity.d.ts +35 -3
  22. package/dist/experimental-identity.d.ts.map +1 -1
  23. package/dist/experimental-identity.js +69 -4
  24. package/dist/experimental-identity.js.map +1 -1
  25. package/dist/models.d.ts +2 -0
  26. package/dist/models.d.ts.map +1 -1
  27. package/dist/models.js +202 -29
  28. package/dist/models.js.map +1 -1
  29. package/dist/options.d.ts +14 -0
  30. package/dist/options.d.ts.map +1 -1
  31. package/dist/options.js +93 -3
  32. package/dist/options.js.map +1 -1
  33. package/dist/prompt.d.ts.map +1 -1
  34. package/dist/prompt.js +50 -1
  35. package/dist/prompt.js.map +1 -1
  36. package/dist/types.d.ts +2 -2
  37. package/package.json +2 -2
  38. package/src/catalogue/catalogue.ts +6 -0
  39. package/src/catalogue/derive.ts +80 -9
  40. package/src/catalogue/experimental-free.ts +4 -20
  41. package/src/catalogue/fetch.ts +16 -5
  42. package/src/client.ts +27 -20
  43. package/src/experimental-identity.ts +73 -5
  44. package/src/models.review.json +6 -15
  45. package/src/models.ts +204 -29
  46. package/src/options.ts +103 -3
  47. package/src/prompt.ts +56 -1
  48. package/src/types.ts +2 -2
@@ -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
- /** Pinned to the OpenCode request shape observed in the 2026-09-28 trial. */
6
- export const EXPERIMENTAL_OPENCODE_USER_AGENT =
7
- 'opencode/1.18.32 ai-sdk/provider-utils/4.0.23 runtime/bun/1.3.14'
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 % ALPHABET.length]).join('')
81
+ return Array.from(bytes, (byte) => ALPHABET[byte % 62]).join('')
19
82
  }
20
83
 
21
- /** Stable when the CLI constructs a new provider for each turn of one conversation. */
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)
@@ -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. */
package/src/options.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { getModelCapabilities as getMessagesCapabilities } from '@ai-sdk/anthropic/internal'
2
2
  import type { LanguageModelV3CallOptions, SharedV3ProviderOptions } from '@ai-sdk/provider'
3
3
  import { type ChatCompletionParams, ProviderRequestError, type ProviderRoute } from '@namzu/sdk'
4
+ import { ZEN_FREE_TIER_TOOL_NAMES } from './experimental-identity.js'
4
5
  import type { ZenModel, ZenProtocol, ZenService } from './models.js'
5
6
  import { isMuseSparkResponsesModel, toModelPrompt } from './prompt.js'
6
7
 
@@ -28,8 +29,15 @@ export function createCallOptions(
28
29
  detail,
29
30
  })
30
31
  }
31
- let maxOutputTokens = params.maxTokens ?? 4096
32
- if (!Number.isSafeInteger(maxOutputTokens) || maxOutputTokens <= 0)
32
+ // Optional wire fields can still select a small upstream default when
33
+ // absent. Request the advertised model ceiling when known; do not invent
34
+ // a ceiling for an unknown model. Messages also checks its native ceiling.
35
+ let maxOutputTokens =
36
+ params.maxTokens ?? (protocol === 'messages' ? undefined : known?.maxOutputTokens)
37
+ if (
38
+ maxOutputTokens !== undefined &&
39
+ (!Number.isSafeInteger(maxOutputTokens) || maxOutputTokens <= 0)
40
+ )
33
41
  refuse('maxTokens must be a positive integer.')
34
42
  if (
35
43
  params.effort !== undefined &&
@@ -133,6 +141,18 @@ export function createCallOptions(
133
141
  case 'messages': {
134
142
  const thinking = params.thinking
135
143
  const capabilities = getMessagesCapabilities(params.model)
144
+ // Messages requires max_tokens. Use the actual model ceiling, not
145
+ // the native adapter's conservative fallback for unknown models.
146
+ if (maxOutputTokens === undefined) {
147
+ const modelCeiling =
148
+ known?.maxOutputTokens ??
149
+ (capabilities.isKnownModel ? capabilities.maxOutputTokens : undefined)
150
+ if (modelCeiling === undefined)
151
+ return refuse('This messages model has no known output limit; set maxTokens explicitly.')
152
+ maxOutputTokens = capabilities.isKnownModel
153
+ ? Math.min(modelCeiling, capabilities.maxOutputTokens)
154
+ : modelCeiling
155
+ }
136
156
  const isThinking = thinking?.type === 'enabled' || thinking?.type === 'adaptive'
137
157
  if (
138
158
  (isThinking || capabilities.rejectsSamplingParameters) &&
@@ -245,7 +265,7 @@ export function createCallOptions(
245
265
  const enforced = new Set(params.enforceToolInputSchema)
246
266
  return {
247
267
  prompt: toModelPrompt(params, route, service, protocol),
248
- maxOutputTokens,
268
+ ...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
249
269
  ...(params.temperature !== undefined ? { temperature: params.temperature } : {}),
250
270
  ...(params.topP !== undefined ? { topP: params.topP } : {}),
251
271
  ...(params.topK !== undefined ? { topK: params.topK } : {}),
@@ -294,3 +314,83 @@ export function createCallOptions(
294
314
  providerOptions: options,
295
315
  }
296
316
  }
317
+
318
+ /**
319
+ * The tool definitions the Zen free-tier gate requires on the wire.
320
+ *
321
+ * The gate admits a request only when its `tools` carry all four of `bash`,
322
+ * `glob`, `grep` and `read`, so a caller that registers none of them — a text
323
+ * turn, or an agent whose tools happen to use other names — would be refused
324
+ * before the model ever saw the prompt. These are the gap-fillers, and only
325
+ * the gaps: a name the caller already registered keeps its own definition,
326
+ * description and schema untouched.
327
+ */
328
+ function zenFreeTierTools(): readonly NonNullable<LanguageModelV3CallOptions['tools']>[number][] {
329
+ return ZEN_FREE_TIER_TOOL_NAMES.map((name) => ({
330
+ type: 'function' as const,
331
+ name,
332
+ description:
333
+ name === 'bash'
334
+ ? 'Execute a shell command and return its standard output and error.'
335
+ : name === 'glob'
336
+ ? 'List filesystem paths matching a glob pattern.'
337
+ : name === 'grep'
338
+ ? 'Search file contents for a regular expression and return the matches.'
339
+ : 'Read a UTF-8 text file and return its contents.',
340
+ inputSchema: {
341
+ type: 'object' as const,
342
+ properties:
343
+ name === 'bash'
344
+ ? { command: { type: 'string' as const, description: 'The shell command to run.' } }
345
+ : name === 'glob'
346
+ ? { pattern: { type: 'string' as const, description: 'The glob pattern to match.' } }
347
+ : name === 'grep'
348
+ ? {
349
+ pattern: {
350
+ type: 'string' as const,
351
+ description: 'The regular expression to search for.',
352
+ },
353
+ }
354
+ : { path: { type: 'string' as const, description: 'The file to read.' } },
355
+ required: [
356
+ name === 'bash'
357
+ ? 'command'
358
+ : name === 'glob'
359
+ ? 'pattern'
360
+ : name === 'grep'
361
+ ? 'pattern'
362
+ : 'path',
363
+ ],
364
+ },
365
+ }))
366
+ }
367
+
368
+ /**
369
+ * Admit an anonymous Zen request through the free-tier gate.
370
+ *
371
+ * `enabled` is the gate path — anonymous access on the official host. When a
372
+ * caller registered no tool at all, the injected names also pin tool choice to
373
+ * `none`: the four names are present because the gate reads them, and the
374
+ * model is still told not to call any of them, so a turn that never asked for
375
+ * tools never receives one it cannot execute. `honoursNone` is false on a wire
376
+ * that rejects the choice — Muse Spark's Answers endpoint accepts only
377
+ * `auto` — where the tools travel without a choice and the kernel's ordinary
378
+ * unknown-tool answer covers a call to a name it does not hold.
379
+ * A caller that did register tools keeps its own choice.
380
+ */
381
+ export function withZenFreeTierTools(
382
+ options: LanguageModelV3CallOptions,
383
+ enabled: boolean,
384
+ honoursNone = true,
385
+ ): LanguageModelV3CallOptions {
386
+ if (!enabled) return options
387
+ const existing = options.tools ?? []
388
+ const held = new Set(existing.flatMap((tool) => (tool.type === 'function' ? [tool.name] : [])))
389
+ const missing = zenFreeTierTools().filter((tool) => !held.has(tool.name))
390
+ if (missing.length === 0) return options
391
+ return {
392
+ ...options,
393
+ tools: [...existing, ...missing],
394
+ ...(existing.length === 0 && honoursNone ? { toolChoice: { type: 'none' as const } } : {}),
395
+ }
396
+ }