@zerotal/arch 1.11.1 → 1.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/ai.md CHANGED
@@ -138,9 +138,28 @@ response.stopReason; // "end_turn" | "max_tokens" | "tool_use" | …
138
138
 
139
139
  Current Claude models **reject** `temperature`, `top_p`, and `top_k` with a 400 — a
140
140
  generic sampling parameter forwarded blindly fails every request. The Anthropic
141
- driver therefore drops `temperature` and warns once.
141
+ driver drops `temperature` on those models and warns once.
142
142
 
143
- Reach for `effort` instead. It trades thoroughness against cost and latency:
143
+ **On models that accept it, it is sent.** The 4.6 and 4.5 generations take sampling
144
+ parameters perfectly well, and there the configured or per-request `temperature`
145
+ reaches the API. Ask `modelCapabilities(model)` if you want to know which you are on:
146
+
147
+ ```ts
148
+ import { modelCapabilities } from "@zerotal/ai";
149
+
150
+ const caps = modelCapabilities("claude-haiku-4-5");
151
+ // { sampling: true, effort: false, thinking: "budget" }
152
+ ```
153
+
154
+ That table is also what keeps the driver from sending a model something it rejects.
155
+ `effort` is a 400 on the 4.5 generation, and those models want an explicit thinking
156
+ budget rather than the adaptive form — so the driver builds a different request for
157
+ them rather than one request for everything. Models it does not recognise are treated
158
+ as current generation, because the ones that differ are a closed set that ages out
159
+ while new models keep arriving.
160
+
161
+ Reach for `effort` where the model has it. It trades thoroughness against cost and
162
+ latency:
144
163
 
145
164
  | Effort | Use it for |
146
165
  | -------- | -------------------------------------------------------- |
@@ -150,6 +169,18 @@ Reach for `effort` instead. It trades thoroughness against cost and latency:
150
169
  | `xhigh` | Hard coding and agentic tasks |
151
170
  | `max` | When correctness matters more than the bill |
152
171
 
172
+ ### The thinking stream
173
+
174
+ A streamed `thinking` chunk carries the model's reasoning as it happens. The API
175
+ **omits that text by default** on the current generation, so the driver asks for it:
176
+ `drivers.anthropic.thinkingDisplay` defaults to `"summarized"`.
177
+
178
+ Set it to `"omitted"` to get the API's own default back. The thinking happens — and is
179
+ billed — either way; the setting only decides whether you are shown it. Before 1.11.2
180
+ the driver never asked, so the documented `thinking` chunk fired forever with
181
+ `text: ""` and no error, and a "thinking…" view built against the 4.6 models stopped
182
+ working when their users moved to 5 without anything saying so.
183
+
153
184
  ### Streaming
154
185
 
155
186
  ```ts fragment
@@ -531,6 +562,8 @@ sections above; this is the index.
531
562
  | `modelPrice` | The price for a model, or `undefined` when we have none. |
532
563
  | `estimateCost` | Estimated USD for one request's usage. Returns 0 for an unpriced model. |
533
564
  | `modelRejectsSampling` | Whether a Claude model rejects `temperature` / `top_p` / `top_k` with a 400. |
565
+ | `modelCapabilities` | What a model accepts: sampling, `effort`, and which thinking shape. |
566
+ | `ModelCapabilities` | The three answers `modelCapabilities` returns. |
534
567
 
535
568
  ### Spend and statistics
536
569
 
package/docs/changelog.md CHANGED
@@ -27,6 +27,93 @@ the section for every version you cross and apply its migration notes, not only
27
27
  majors. [Releases and versioning](/docs/support-policy#releases-and-versioning) explains
28
28
  when that carve-out ends.
29
29
 
30
+ ## 1.11.2 — 2026-08-31
31
+
32
+ `@zerotal/ai` is `stable`, and the release that promotes it is the one that fixes five
33
+ bugs its first production users found. That ordering is the point: a `stable` promise
34
+ about an API nothing has pushed against is a promise nobody has tested.
35
+
36
+ Also here: two gates that were not doing their job, one of which had let two releases
37
+ publish over a red build.
38
+
39
+ A patch. Nothing here breaks — `@zerotal/ai`'s surface was narrowed _before_ the label,
40
+ while narrowing was still free.
41
+
42
+ ### `@zerotal/ai` — the review, answered
43
+
44
+ The package shipped `experimental` with a stated precondition — _it graduates in the
45
+ release after its first real users_ — and a review date of 1.11.0 enforced by the
46
+ package linter rather than by a promise. Its first production users, running it against
47
+ Anthropic, sent a field review of the driver. So the precondition was met rather than
48
+ waived, and the answer is **promote**.
49
+
50
+ **Fixed, all from that review:**
51
+
52
+ - **Sonnet 5 was priced as Sonnet 4.6** — 3/15 rather than 2/10, 50% high. The same
53
+ table feeds `limits.perRequestUsd` and `perDayUsd`, so an app on that model was
54
+ refused requests comfortably inside its budget by an error that said "spend limit"
55
+ and sent it to its config rather than to the row. `AiSpendLimitError` now quotes the
56
+ rate it priced with and names `registerModelPrice()`, so a wrong table is legible
57
+ from the refusal and correctable without waiting for a release.
58
+
59
+ - **`effort` and `thinking` are model-aware.** Both went on every call. `effort` is a
60
+ 400 on the 4.5 generation and those models want an explicit thinking budget rather
61
+ than the adaptive form — so the package listed `claude-haiku-4-5` in its pricing
62
+ table while the driver could not successfully call it. `modelCapabilities()` answers
63
+ what a model takes, and the driver builds the request that model accepts.
64
+
65
+ - **`temperature` never reached the API, on any model.** Not in the review — it turned
66
+ up while testing the item below. The driver warned about dropping `temperature` and
67
+ had no branch that set it, so the configured default and `AiRequest.temperature` were
68
+ both inert everywhere, including on the models that accept them. The old predicate
69
+ warned for almost every model, which is exactly what made the silence look deliberate
70
+ on the few it did not.
71
+
72
+ - **The streamed `thinking` chunk was always empty.** The API omits thinking text by
73
+ default on the current generation, so a documented chunk type fired forever with
74
+ `text: ""` and no error — and a "thinking…" view built against the 4.6 models, where
75
+ it defaulted on, stopped working when users moved to 5 with nothing to say so.
76
+ `drivers.anthropic.thinkingDisplay` defaults to `"summarized"`.
77
+
78
+ - **An app with no AI configured now boots.** `AiConfig` threw when no driver was
79
+ declared and threw again on an empty `apiKey`, so a deployment with no key could not
80
+ express itself either way. One app declared an Ollama server it did not run purely to
81
+ satisfy the validator, with a comment explaining that the config was lying. "AI is
82
+ off" is a coherent deployment and is now expressible; the first call raises
83
+ `AiDriverUnavailableError`, whose `transient` is already `false`.
84
+
85
+ - **`countTokens` returns `null` where a provider cannot count**, rather than `0`. Only
86
+ Anthropic has a counting endpoint, and `0` is also a real count for an empty prompt.
87
+
88
+ **How it was promoted**, because the order is the part that matters:
89
+
90
+ The surface was narrowed **first** — narrowing after `stable` is itself a breaking
91
+ change. `toSchema`, `strippedConstraints`, `resetSpend` and `resetStats` are `@internal`
92
+ now: still exported, so nothing breaks at runtime, but no longer promised.
93
+ `translateSchema` stayed public despite having no caller outside the package, for the
94
+ same reason `AiDriver` is public — the point of a driver contract is that someone else
95
+ implements it, and implementing structured output means translating a schema.
96
+ `AiDelivery` stayed too, being the element type of `recentGenerations()`.
97
+
98
+ Then the two modules it would have been embarrassing to freeze untested: the SSE
99
+ parser, which reads a remote provider's framing off the network, and prompt redaction,
100
+ which is the only thing between a user's prompt and a log that outlives the request.
101
+ Both hold up — the parser reassembles a frame whose terminator is split across chunks
102
+ and a UTF-8 sequence cut mid-character.
103
+
104
+ ### Fixed — the gates
105
+
106
+ - **The release workflow ran three checks; the pull-request workflow ran fifteen.** So
107
+ every convention, surface and documentation gate guarded the cheap, reversible action
108
+ and not the permanent one. 1.11.0 and 1.11.1 both published over a CI that had been
109
+ red since the first of them, and nothing in the release objected, because nothing in
110
+ the release looked. `release.yml` now runs the same set.
111
+
112
+ - **One failing check hid eleven others.** When the `@zerotal/ai` review fell due, the
113
+ package-conventions step failed and every later step in that job was skipped —
114
+ reported as "skipped", which reads like "not applicable" rather than "never ran".
115
+ Each check is now guarded so it reports its own result.
116
+
30
117
  ## 1.11.1 — 2026-08-31
31
118
 
32
119
  Two things the framework could not do, both reported by teams who had already
@@ -111,32 +111,39 @@ has worn the label for a year is not being cautious, it is unowned. So each one
111
111
  below `stable` names the release by which it is reviewed, and the review has three
112
112
  outcomes — promote, keep with a new date and the reason, or withdraw.
113
113
 
114
- | Package | Now | Reviewed by |
115
- | ------------- | -------------- | ----------- |
116
- | `@zerotal/ai` | `experimental` | **1.11.0** |
117
-
118
- `@zerotal/ai`'s date moved once, from 1.9.0, and the reason is the same one that made it
119
- experimental in the first place: **it graduates in the release after its first real users, and
120
- it has not had them yet.** Promoting on a date rather than on evidence is how a label becomes
121
- decoration a `stable` promise is only worth making about an API that something has pushed
122
- against.
123
-
124
- The date is a forcing function, not a prediction. It moved once; a second move needs a better
125
- reason than the first, or the honest answer is to withdraw the package rather than keep
126
- re-dating it. Its surface triage and the tests for its SSE parser and prompt redaction are worth
127
- doing meanwhile, and are tracked separately they improve the package whichever way the review
128
- goes, and they are what would otherwise turn the deadline into a scramble.
114
+ Nothing is below `stable` today. The table that lived here is empty, which is the
115
+ outcome the mechanism is for rather than the absence of one — it is how the review
116
+ looks when every date has been answered.
117
+
118
+ `@zerotal/ai` was the last entry, `experimental` and due by 1.11.0. It was promoted in
119
+ 1.11.2, and the precondition it carried was met rather than waived: it graduates in the
120
+ release after its first real users, and its first production users sent a field review
121
+ of the driver against Anthropic. That review is why the promotion is worth anything
122
+ five bugs came back with it, and a `stable` promise about an API nothing has pushed
123
+ against is a promise nobody has tested.
124
+
125
+ The order was deliberate. Its surface was narrowed **before** the label, because
126
+ narrowing after `stable` is itself a breaking change: `toSchema`, `strippedConstraints`,
127
+ `resetSpend` and `resetStats` are `@internal` now. `translateSchema` stayed public
128
+ despite having no caller outside the package, for the same reason `AiDriver` did — the
129
+ whole point of a driver contract is that someone else implements it, and implementing
130
+ structured output requires translating a schema. Then its two riskiest modules were
131
+ tested: the SSE parser, which reads a remote provider's framing off the network, and
132
+ prompt redaction, which is the only thing between a user's prompt and a log that
133
+ outlives the request.
129
134
 
130
135
  `@zerotal/arch` held `beta` with the same date, was reviewed ahead of it, and is
131
136
  `stable` — the release that carried the promotion is the one its
132
- [changelog](/docs/changelog) names. Its surface was narrowed first the writers behind
133
- `arch:install` are `@internal` now, because they had no caller outside the package
137
+ [changelog](/docs/changelog) names. Its surface was narrowed first too: the writers
138
+ behind `arch:install` are `@internal`, because they had no caller outside the package
134
139
  and freezing them would have promised the shape of `.mcp.json` writing to nobody.
135
140
 
136
- `@zerotal/ai` is not in the `zerotal` meta-package and nothing `stable` depends on
137
- it, so the cost of its label falling due is ours and not yours. Neither is `arch`,
138
- still: `arch:install` writes configuration and instruction files into a project,
139
- which is an opinion about someone's toolchain and stays their choice to invite.
141
+ Neither `ai` nor `arch` is in the `zerotal` meta-package. `arch` stays out because
142
+ `arch:install` writes configuration and instruction files into a project, which is an
143
+ opinion about someone's toolchain and stays their choice to invite. `ai` stays out for
144
+ its own reason rather than by omission: it is the only package with an optional peer
145
+ on a vendor SDK, and pulling it into the meta-package would put a provider dependency
146
+ in front of every app that installs `zerotal`, including the ones with no AI in them.
140
147
 
141
148
  That table used to be the whole of the commitment, which meant the version could
142
149
  sail past it and the only consequence would be this paragraph quietly becoming
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerotal/arch",
3
- "version": "1.11.1",
3
+ "version": "1.11.2",
4
4
  "license": "MIT",
5
5
  "maturity": "stable",
6
6
  "private": false,
@@ -35,11 +35,11 @@
35
35
  "typecheck": "tsc --noEmit"
36
36
  },
37
37
  "dependencies": {
38
- "@zerotal/core": "1.11.1"
38
+ "@zerotal/core": "1.11.2"
39
39
  },
40
40
  "devDependencies": {
41
41
  "typescript": "^5.8.0",
42
- "@zerotal/orm": "1.11.1"
42
+ "@zerotal/orm": "1.11.2"
43
43
  },
44
44
  "description": "The Zerotal agent surface — an MCP server that hands coding agents the framework's machine-readable truth: exact API signatures, live routes and schema, version-matched docs, and `zt doctor`.",
45
45
  "keywords": [