@tormentalabs/claude-code-wire-compat 0.1.0-rc.17 → 0.2.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.
- package/CHANGELOG.md +101 -2
- package/README.md +67 -2
- package/dist/betas.d.ts +37 -0
- package/dist/betas.d.ts.map +1 -1
- package/dist/betas.js +55 -23
- package/dist/betas.js.map +1 -1
- package/dist/build-request.d.ts +11 -0
- package/dist/build-request.d.ts.map +1 -1
- package/dist/build-request.js +133 -11
- package/dist/build-request.js.map +1 -1
- package/dist/contracts.d.ts +53 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/fingerprint.d.ts +29 -2
- package/dist/fingerprint.d.ts.map +1 -1
- package/dist/fingerprint.js +60 -7
- package/dist/fingerprint.js.map +1 -1
- package/dist/headers.d.ts.map +1 -1
- package/dist/headers.js +12 -4
- package/dist/headers.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/model-capabilities.d.ts +31 -3
- package/dist/model-capabilities.d.ts.map +1 -1
- package/dist/model-capabilities.js +145 -12
- package/dist/model-capabilities.js.map +1 -1
- package/dist/models.d.ts.map +1 -1
- package/dist/models.js +8 -4
- package/dist/models.js.map +1 -1
- package/dist/profile-behaviors.d.ts +61 -0
- package/dist/profile-behaviors.d.ts.map +1 -0
- package/dist/profile-behaviors.js +53 -0
- package/dist/profile-behaviors.js.map +1 -0
- package/dist/profiles/beta-registry-2.1.233.d.ts +140 -0
- package/dist/profiles/beta-registry-2.1.233.d.ts.map +1 -0
- package/dist/profiles/beta-registry-2.1.233.js +183 -0
- package/dist/profiles/beta-registry-2.1.233.js.map +1 -0
- package/dist/profiles/claude-code-2.1.195.d.ts.map +1 -1
- package/dist/profiles/claude-code-2.1.195.js +14 -0
- package/dist/profiles/claude-code-2.1.195.js.map +1 -1
- package/dist/profiles/claude-code-2.1.233.d.ts +3 -0
- package/dist/profiles/claude-code-2.1.233.d.ts.map +1 -0
- package/dist/profiles/claude-code-2.1.233.js +235 -0
- package/dist/profiles/claude-code-2.1.233.js.map +1 -0
- package/dist/redaction.d.ts.map +1 -1
- package/dist/redaction.js +14 -1
- package/dist/redaction.js.map +1 -1
- package/dist/request-body.d.ts.map +1 -1
- package/dist/request-body.js +12 -10
- package/dist/request-body.js.map +1 -1
- package/dist/thinking.d.ts +33 -7
- package/dist/thinking.d.ts.map +1 -1
- package/dist/thinking.js +105 -36
- package/dist/thinking.js.map +1 -1
- package/package.json +10 -2
- package/src/anti-verbosity.ts +219 -0
- package/src/beta-registry.ts +140 -0
- package/src/betas.ts +302 -0
- package/src/build-request.ts +1799 -0
- package/src/contracts.ts +1286 -0
- package/src/count-tokens.ts +84 -0
- package/src/fingerprint.ts +155 -0
- package/src/headers.ts +448 -0
- package/src/index.ts +63 -0
- package/src/metadata.ts +331 -0
- package/src/model-capabilities.ts +453 -0
- package/src/model-identity.ts +45 -0
- package/src/models.ts +50 -0
- package/src/profile-behaviors.ts +114 -0
- package/src/profiles/beta-registry-2.1.233.ts +200 -0
- package/src/profiles/claude-code-2.1.195.ts +168 -0
- package/src/profiles/claude-code-2.1.233.ts +240 -0
- package/src/redaction.ts +536 -0
- package/src/request-body.ts +1931 -0
- package/src/sha256.ts +114 -0
- package/src/system-prompt.ts +222 -0
- package/src/thinking.ts +346 -0
- package/src/unicode.ts +24 -0
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
ClaudeCodeCapabilities,
|
|
5
|
+
ClaudeCodeCatalogueEntry,
|
|
6
|
+
ClaudeCodeProtocolProfile,
|
|
7
|
+
} from "./contracts.js";
|
|
8
|
+
import { profileBehaviors } from "./profile-behaviors.js";
|
|
9
|
+
import { CLAUDE_CODE_2_1_195_PROFILE } from "./profiles/claude-code-2.1.195.js";
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* Capability derivation, ported from the genuine client's nine capability
|
|
13
|
+
* predicates.
|
|
14
|
+
*
|
|
15
|
+
* READ THIS FIRST -- there are two derivation paths and they are not
|
|
16
|
+
* interchangeable:
|
|
17
|
+
*
|
|
18
|
+
* 1. Catalogue path (`deriveCapabilitiesFromCatalogue`), taken for every id
|
|
19
|
+
* present in the 2.1.195 catalogue. Six of the nine capabilities have a
|
|
20
|
+
* verbatim upstream string in `ClaudeCodeCatalogueEntry.capabilities`
|
|
21
|
+
* (`effort`, `max_effort`, `xhigh_effort`, `adaptive_thinking`,
|
|
22
|
+
* `context_management`, `rejects_disabled_thinking`) and are read from
|
|
23
|
+
* there. The other three (`thinking`, `interleavedThinking`,
|
|
24
|
+
* `temperature`) have NO catalogue string in any client version and stay
|
|
25
|
+
* predicate-derived.
|
|
26
|
+
* 2. Predicate fallback (`deriveCapabilitiesFromPredicates`), taken for ids
|
|
27
|
+
* with no catalogue entry -- `claude-mythos-5` (absent by product
|
|
28
|
+
* decision D-1), ids from a newer client, and anything that escaped
|
|
29
|
+
* normalization. Those fall through every exclusion list and resolve
|
|
30
|
+
* maximally permissive, `temperature` excepted because its predicate is
|
|
31
|
+
* an allowlist.
|
|
32
|
+
*
|
|
33
|
+
* The two paths agree on every catalogue cell but one; see the demarcated
|
|
34
|
+
* C1 block in `deriveCapabilities`. `test/validation/capability-equivalence
|
|
35
|
+
* .test.ts` pins the agreement cell by cell and pins that one divergence from
|
|
36
|
+
* both sides, so neither path can drift silently.
|
|
37
|
+
*
|
|
38
|
+
* THE LOAD-BEARING FACT about the predicates, stated up front because it is
|
|
39
|
+
* surprising:
|
|
40
|
+
*
|
|
41
|
+
* On the first-party provider -- the only provider this package targets --
|
|
42
|
+
* every one of these nine predicates reduces to a pure function of the
|
|
43
|
+
* normalized model id.
|
|
44
|
+
*
|
|
45
|
+
* Why. Upstream, each predicate has the shape
|
|
46
|
+
*
|
|
47
|
+
* let override = W9(model, cap); // env-var capability override
|
|
48
|
+
* if (override !== undefined) return override;
|
|
49
|
+
* let id = mo(model); // normalize
|
|
50
|
+
* if (<exclusion list>) return false;
|
|
51
|
+
* if (JB(id, cap) || id === "claude-mythos-5") return true;
|
|
52
|
+
* return ZO(l_(model)); // provider fallback
|
|
53
|
+
*
|
|
54
|
+
* and in this package:
|
|
55
|
+
*
|
|
56
|
+
* - `W9` opens with `if (td()) return;` and `td()` is true for first party,
|
|
57
|
+
* so the override always yields `undefined`. It also reads environment
|
|
58
|
+
* variables, which this package is designed never to do. Guard dropped.
|
|
59
|
+
* - `ZO(...)` is `provider is firstParty|anthropicAws|foundry|mantle`. The
|
|
60
|
+
* profile pins `provider: "anthropic"`, so `ZO(...)` is unconditionally
|
|
61
|
+
* true and `return ZO(l_(e))` becomes `return true`.
|
|
62
|
+
* - Therefore the `JB(id, cap)` catalogue-membership test and the
|
|
63
|
+
* `claude-mythos-5` special case can only return `true` from a position
|
|
64
|
+
* where the fallback already returns `true`. They are unobservable.
|
|
65
|
+
* - `l_(e) === "foundry"` branches are unreachable for the same reason.
|
|
66
|
+
* - `ut(CLAUDE_CODE_ALWAYS_ENABLE_EFFORT)` reads an environment variable;
|
|
67
|
+
* dropped by the same design rule as `W9`.
|
|
68
|
+
*
|
|
69
|
+
* Only the leading exclusion list is observable, so that is all these
|
|
70
|
+
* functions contain.
|
|
71
|
+
*
|
|
72
|
+
* WARNING TO FUTURE READERS. Two things follow that look like bugs and are not:
|
|
73
|
+
*
|
|
74
|
+
* 1. The individual predicates below still carry no `JB`-equivalent
|
|
75
|
+
* membership test, and must not grow one. Catalogue membership is
|
|
76
|
+
* consulted in exactly one place -- `deriveCapabilitiesFromCatalogue` --
|
|
77
|
+
* so the fallback path stays a pure function of the id and the
|
|
78
|
+
* equivalence between the two paths stays testable. A membership check
|
|
79
|
+
* inside a predicate would be a dead branch on the catalogue path and an
|
|
80
|
+
* unreachable one on the fallback path.
|
|
81
|
+
* 2. The catalogue `capabilities` arrays carry strings this module does not
|
|
82
|
+
* map to a `ClaudeCodeCapabilities` field -- `fast_mode`, `lean_prompt`,
|
|
83
|
+
* `fable_5_mitigations` and `mid_conv_system`. They are faithful
|
|
84
|
+
* transcribed evidence and are consumed elsewhere. Do NOT delete them
|
|
85
|
+
* because this module ignores them.
|
|
86
|
+
*
|
|
87
|
+
* `claude-mythos-5` has no catalogue entry by product decision D-1. Upstream
|
|
88
|
+
* special-cases it by name in `Kw`, `Hke`, `Yte` and `Uot`; this port subsumes
|
|
89
|
+
* those clauses into the first-party fallback, which yields an identical
|
|
90
|
+
* result. The explicit D-1 test asserting its full nine-boolean row is the
|
|
91
|
+
* guard for that equivalence.
|
|
92
|
+
*
|
|
93
|
+
* Model ids reaching these functions have already been normalized by
|
|
94
|
+
* `resolveModel` via `normalizeModelId`.
|
|
95
|
+
*/
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Upstream `Kw` at byte offset 227719902.
|
|
99
|
+
*
|
|
100
|
+
* Elided: the `W9` override, `CLAUDE_CODE_ALWAYS_ENABLE_EFFORT`, the
|
|
101
|
+
* `JB(id, "effort") || id === "claude-mythos-5"` test, and the `ZO` fallback.
|
|
102
|
+
*/
|
|
103
|
+
export function supportsEffort(normalizedId: string): boolean {
|
|
104
|
+
if (
|
|
105
|
+
normalizedId.includes("claude-3-") ||
|
|
106
|
+
normalizedId === "claude-opus-4-0" ||
|
|
107
|
+
normalizedId === "claude-opus-4-1" ||
|
|
108
|
+
normalizedId === "claude-sonnet-4-0" ||
|
|
109
|
+
normalizedId === "claude-sonnet-4-5" ||
|
|
110
|
+
normalizedId === "claude-haiku-4-5"
|
|
111
|
+
) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Upstream `Hke` at byte offset 227720257.
|
|
119
|
+
*
|
|
120
|
+
* Its exclusion list is `Kw`'s plus `claude-opus-4-5`. Elided: the `W9`
|
|
121
|
+
* override, the `JB(id, "max_effort") || id === "claude-mythos-5"` test, and
|
|
122
|
+
* the `ZO` fallback.
|
|
123
|
+
*/
|
|
124
|
+
export function supportsMaxEffort(normalizedId: string): boolean {
|
|
125
|
+
if (
|
|
126
|
+
normalizedId.includes("claude-3-") ||
|
|
127
|
+
normalizedId === "claude-opus-4-0" ||
|
|
128
|
+
normalizedId === "claude-opus-4-1" ||
|
|
129
|
+
normalizedId === "claude-opus-4-5" ||
|
|
130
|
+
normalizedId === "claude-sonnet-4-0" ||
|
|
131
|
+
normalizedId === "claude-sonnet-4-5" ||
|
|
132
|
+
normalizedId === "claude-haiku-4-5"
|
|
133
|
+
) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Upstream `Yte` at byte offset 227720583.
|
|
141
|
+
*
|
|
142
|
+
* Its exclusion list is `Hke`'s plus `claude-opus-4-6` and
|
|
143
|
+
* `claude-sonnet-4-6`. Elided: the `W9` override, the
|
|
144
|
+
* `JB(id, "xhigh_effort") || id === "claude-mythos-5"` test, and the `ZO`
|
|
145
|
+
* fallback.
|
|
146
|
+
*/
|
|
147
|
+
export function supportsXhighEffort(normalizedId: string): boolean {
|
|
148
|
+
if (
|
|
149
|
+
normalizedId.includes("claude-3-") ||
|
|
150
|
+
normalizedId === "claude-opus-4-0" ||
|
|
151
|
+
normalizedId === "claude-opus-4-1" ||
|
|
152
|
+
normalizedId === "claude-opus-4-5" ||
|
|
153
|
+
normalizedId === "claude-opus-4-6" ||
|
|
154
|
+
normalizedId === "claude-sonnet-4-0" ||
|
|
155
|
+
normalizedId === "claude-sonnet-4-5" ||
|
|
156
|
+
normalizedId === "claude-sonnet-4-6" ||
|
|
157
|
+
normalizedId === "claude-haiku-4-5"
|
|
158
|
+
) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Upstream `Uot` at byte offset 227383245.
|
|
166
|
+
*
|
|
167
|
+
* Its exclusion list is `Kw`'s plus `claude-opus-4-5`, matching `Hke`'s today.
|
|
168
|
+
* The two are transcribed separately on purpose: they are independent upstream
|
|
169
|
+
* predicates that happen to agree at this client version. Elided: the `W9`
|
|
170
|
+
* override, the `JB(id, "adaptive_thinking") || id === "claude-mythos-5"`
|
|
171
|
+
* test, and the `ZO` fallback.
|
|
172
|
+
*/
|
|
173
|
+
export function supportsAdaptiveThinking(normalizedId: string): boolean {
|
|
174
|
+
if (
|
|
175
|
+
normalizedId.includes("claude-3-") ||
|
|
176
|
+
normalizedId === "claude-opus-4-0" ||
|
|
177
|
+
normalizedId === "claude-opus-4-1" ||
|
|
178
|
+
normalizedId === "claude-opus-4-5" ||
|
|
179
|
+
normalizedId === "claude-sonnet-4-0" ||
|
|
180
|
+
normalizedId === "claude-sonnet-4-5" ||
|
|
181
|
+
normalizedId === "claude-haiku-4-5"
|
|
182
|
+
) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Upstream `D9r` at byte offset 227382784.
|
|
190
|
+
*
|
|
191
|
+
* Reduces to the same body as `supportsInterleavedThinking` and
|
|
192
|
+
* `supportsContextManagement` at this client version. Kept separate on
|
|
193
|
+
* purpose: three independent upstream predicates with different provenance
|
|
194
|
+
* that are versioned independently upstream. Do not merge them.
|
|
195
|
+
*
|
|
196
|
+
* Elided: the `W9` override.
|
|
197
|
+
*/
|
|
198
|
+
export function supportsThinking(normalizedId: string): boolean {
|
|
199
|
+
return !normalizedId.includes("claude-3-");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Upstream `QOt` at byte offset 227384610.
|
|
204
|
+
*
|
|
205
|
+
* See the note on `supportsThinking` about the three identical bodies.
|
|
206
|
+
* Elided: the `W9` override, the unreachable `foundry` branch, and the
|
|
207
|
+
* non-`ZO` provider tail (`claude-haiku-4-5` is excluded only for providers
|
|
208
|
+
* outside `ZO`, which cannot occur here -- note this is why haiku 4.5 has
|
|
209
|
+
* interleaved thinking on first party but not on, say, vertex).
|
|
210
|
+
*/
|
|
211
|
+
export function supportsInterleavedThinking(normalizedId: string): boolean {
|
|
212
|
+
return !normalizedId.includes("claude-3-");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Upstream `n0d` at byte offset 227385143.
|
|
217
|
+
*
|
|
218
|
+
* See the note on `supportsThinking` about the three identical bodies.
|
|
219
|
+
* Elided: the unreachable `foundry` branch and the non-`ZO` tail
|
|
220
|
+
* `JB(id, "context_management") || id === "claude-mythos-5"`. `n0d` has no
|
|
221
|
+
* `W9` override upstream.
|
|
222
|
+
*/
|
|
223
|
+
export function supportsContextManagement(normalizedId: string): boolean {
|
|
224
|
+
return !normalizedId.includes("claude-3-");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Upstream `j4e` at byte offset 227385302.
|
|
229
|
+
*
|
|
230
|
+
* Elided: the unconditionally true `ZO` provider gate.
|
|
231
|
+
* This beta-only gate is intentionally absent from `ClaudeCodeCapabilities`.
|
|
232
|
+
*
|
|
233
|
+
* Stays predicate-derived and takes no part in the catalogue path: no
|
|
234
|
+
* `structured_outputs` string exists in any catalogue entry, so there is
|
|
235
|
+
* nothing to read. Deliberately takes no profile parameter -- upstream does
|
|
236
|
+
* not gate this beta on the catalogue in any modelled version, so making it
|
|
237
|
+
* profile-aware would invent behaviour rather than port it.
|
|
238
|
+
*/
|
|
239
|
+
export function supportsStructuredOutputs(normalizedId: string): boolean {
|
|
240
|
+
return !(
|
|
241
|
+
normalizedId.includes("claude-3-") ||
|
|
242
|
+
normalizedId === "claude-opus-4-0" ||
|
|
243
|
+
normalizedId === "claude-sonnet-4-0"
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Upstream `RCn` at byte offset 227387413.
|
|
249
|
+
*
|
|
250
|
+
* Elided: host-state gates, the `W9` override, the unobservable `JB`/mythos
|
|
251
|
+
* clause, and the unconditionally true `ZO` fallback. This exclusion list
|
|
252
|
+
* differs from `rejectsDisabledThinking` by one member: that predicate also
|
|
253
|
+
* excludes `claude-opus-4-8`. Do not merge them.
|
|
254
|
+
* This beta-only gate is intentionally absent from `ClaudeCodeCapabilities`.
|
|
255
|
+
*
|
|
256
|
+
* Catalogue-first WHEN a profile is supplied and that profile catalogues the
|
|
257
|
+
* id: `mid_conv_system` exists as a catalogue string, and from 2.1.222+ the
|
|
258
|
+
* catalogue is what upstream reads. The switch is behaviour-preserving for
|
|
259
|
+
* 2.1.195, which is the point of the equivalence pinning in
|
|
260
|
+
* `test/validation/capability-equivalence.test.ts`: in the 2.1.195 catalogue
|
|
261
|
+
* exactly `claude-opus-4-8` and `claude-fable-5` carry the string, and those
|
|
262
|
+
* are exactly the two ids the exclusion list below admits.
|
|
263
|
+
*
|
|
264
|
+
* Without a profile -- or for an id the profile does not catalogue, such as
|
|
265
|
+
* `claude-mythos-5` under 2.1.195 -- the predicate remains authoritative.
|
|
266
|
+
*/
|
|
267
|
+
export function supportsMidConversationSystem(
|
|
268
|
+
normalizedId: string,
|
|
269
|
+
profile?: ClaudeCodeProtocolProfile,
|
|
270
|
+
): boolean {
|
|
271
|
+
const entry = profile?.supportedModels[normalizedId];
|
|
272
|
+
if (entry !== undefined) {
|
|
273
|
+
return entry.capabilities.includes("mid_conv_system");
|
|
274
|
+
}
|
|
275
|
+
return !(
|
|
276
|
+
normalizedId.includes("claude-3-") ||
|
|
277
|
+
normalizedId === "claude-opus-4-0" ||
|
|
278
|
+
normalizedId === "claude-opus-4-1" ||
|
|
279
|
+
normalizedId === "claude-opus-4-5" ||
|
|
280
|
+
normalizedId === "claude-opus-4-6" ||
|
|
281
|
+
normalizedId === "claude-opus-4-7" ||
|
|
282
|
+
normalizedId === "claude-sonnet-4-0" ||
|
|
283
|
+
normalizedId === "claude-sonnet-4-5" ||
|
|
284
|
+
normalizedId === "claude-sonnet-4-6" ||
|
|
285
|
+
normalizedId === "claude-haiku-4-5"
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Upstream `LCn` at byte offset 227385451.
|
|
291
|
+
*
|
|
292
|
+
* INVERTED POLARITY relative to every other predicate in this module: this
|
|
293
|
+
* list is an ALLOWLIST. Membership means temperature IS supported. Do not
|
|
294
|
+
* refactor it into the shared exclusion-list shape.
|
|
295
|
+
*
|
|
296
|
+
* Elided: the `W9` override.
|
|
297
|
+
*/
|
|
298
|
+
export function supportsTemperature(normalizedId: string): boolean {
|
|
299
|
+
return (
|
|
300
|
+
normalizedId.includes("claude-3-") ||
|
|
301
|
+
normalizedId === "claude-opus-4-0" ||
|
|
302
|
+
normalizedId === "claude-opus-4-1" ||
|
|
303
|
+
normalizedId === "claude-opus-4-5" ||
|
|
304
|
+
normalizedId === "claude-opus-4-6" ||
|
|
305
|
+
normalizedId === "claude-sonnet-4-0" ||
|
|
306
|
+
normalizedId === "claude-sonnet-4-5" ||
|
|
307
|
+
normalizedId === "claude-sonnet-4-6" ||
|
|
308
|
+
normalizedId === "claude-haiku-4-5"
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Upstream `U4e` at byte offset 227382881.
|
|
314
|
+
*
|
|
315
|
+
* Its exclusion list is the widest of the five: every catalogue model except
|
|
316
|
+
* `claude-fable-5`. It has no `W9` override and no `claude-mythos-5` clause
|
|
317
|
+
* upstream. Elided: the `JB(id, "rejects_disabled_thinking")` test and the
|
|
318
|
+
* `ZO` fallback.
|
|
319
|
+
*/
|
|
320
|
+
export function rejectsDisabledThinking(normalizedId: string): boolean {
|
|
321
|
+
if (
|
|
322
|
+
normalizedId.includes("claude-3-") ||
|
|
323
|
+
normalizedId === "claude-opus-4-0" ||
|
|
324
|
+
normalizedId === "claude-opus-4-1" ||
|
|
325
|
+
normalizedId === "claude-opus-4-5" ||
|
|
326
|
+
normalizedId === "claude-opus-4-6" ||
|
|
327
|
+
normalizedId === "claude-opus-4-7" ||
|
|
328
|
+
normalizedId === "claude-opus-4-8" ||
|
|
329
|
+
normalizedId === "claude-sonnet-4-0" ||
|
|
330
|
+
normalizedId === "claude-sonnet-4-5" ||
|
|
331
|
+
normalizedId === "claude-sonnet-4-6" ||
|
|
332
|
+
normalizedId === "claude-haiku-4-5"
|
|
333
|
+
) {
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* The six `ClaudeCodeCapabilities` fields the catalogue represents, paired
|
|
341
|
+
* with their verbatim upstream capability string. The three omitted fields --
|
|
342
|
+
* `thinking`, `interleavedThinking`, `temperature` -- have no catalogue
|
|
343
|
+
* string in any client version and are derived from their predicates on both
|
|
344
|
+
* paths.
|
|
345
|
+
*/
|
|
346
|
+
const CATALOGUE_BACKED_CAPABILITIES = {
|
|
347
|
+
effort: "effort",
|
|
348
|
+
maxEffort: "max_effort",
|
|
349
|
+
xhighEffort: "xhigh_effort",
|
|
350
|
+
adaptiveThinking: "adaptive_thinking",
|
|
351
|
+
contextManagement: "context_management",
|
|
352
|
+
rejectsDisabledThinking: "rejects_disabled_thinking",
|
|
353
|
+
} as const;
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Pure catalogue -> capabilities mapping. Reads nothing but `entry` for the
|
|
357
|
+
* six catalogue-backed fields; `normalizedId` is used only for the three
|
|
358
|
+
* fields the catalogue does not represent.
|
|
359
|
+
*
|
|
360
|
+
* This function applies no exceptions and no id special cases. The one cell
|
|
361
|
+
* where the 2.1.195 catalogue disagrees with the wire is corrected by the
|
|
362
|
+
* caller, so that this mapping stays a faithful reading of the data and the
|
|
363
|
+
* correction stays visible at exactly one site.
|
|
364
|
+
*/
|
|
365
|
+
export function deriveCapabilitiesFromCatalogue(
|
|
366
|
+
entry: ClaudeCodeCatalogueEntry,
|
|
367
|
+
normalizedId: string,
|
|
368
|
+
): ClaudeCodeCapabilities {
|
|
369
|
+
const has = (capability: string): boolean =>
|
|
370
|
+
entry.capabilities.includes(capability);
|
|
371
|
+
|
|
372
|
+
return Object.freeze({
|
|
373
|
+
thinking: supportsThinking(normalizedId),
|
|
374
|
+
adaptiveThinking: has(CATALOGUE_BACKED_CAPABILITIES.adaptiveThinking),
|
|
375
|
+
interleavedThinking: supportsInterleavedThinking(normalizedId),
|
|
376
|
+
effort: has(CATALOGUE_BACKED_CAPABILITIES.effort),
|
|
377
|
+
maxEffort: has(CATALOGUE_BACKED_CAPABILITIES.maxEffort),
|
|
378
|
+
xhighEffort: has(CATALOGUE_BACKED_CAPABILITIES.xhighEffort),
|
|
379
|
+
contextManagement: has(CATALOGUE_BACKED_CAPABILITIES.contextManagement),
|
|
380
|
+
temperature: supportsTemperature(normalizedId),
|
|
381
|
+
rejectsDisabledThinking: has(
|
|
382
|
+
CATALOGUE_BACKED_CAPABILITIES.rejectsDisabledThinking,
|
|
383
|
+
),
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Fallback for ids with no catalogue entry. Every field comes from its
|
|
389
|
+
* predicate, which is the pre-catalogue behaviour of this module, preserved
|
|
390
|
+
* byte for byte in result: unknown ids fall through every exclusion list and
|
|
391
|
+
* resolve maximally permissive, `temperature` excepted (allowlist polarity).
|
|
392
|
+
*
|
|
393
|
+
* `claude-mythos-5` reaches this path -- it has no catalogue entry by product
|
|
394
|
+
* decision D-1 -- and upstream special-cases it by name in `Kw`, `Hke`, `Yte`
|
|
395
|
+
* and `Uot`. Those clauses are subsumed here by the first-party fallback,
|
|
396
|
+
* which yields an identical result.
|
|
397
|
+
*/
|
|
398
|
+
function deriveCapabilitiesFromPredicates(
|
|
399
|
+
normalizedId: string,
|
|
400
|
+
): ClaudeCodeCapabilities {
|
|
401
|
+
return Object.freeze({
|
|
402
|
+
thinking: supportsThinking(normalizedId),
|
|
403
|
+
adaptiveThinking: supportsAdaptiveThinking(normalizedId),
|
|
404
|
+
interleavedThinking: supportsInterleavedThinking(normalizedId),
|
|
405
|
+
effort: supportsEffort(normalizedId),
|
|
406
|
+
maxEffort: supportsMaxEffort(normalizedId),
|
|
407
|
+
xhighEffort: supportsXhighEffort(normalizedId),
|
|
408
|
+
contextManagement: supportsContextManagement(normalizedId),
|
|
409
|
+
temperature: supportsTemperature(normalizedId),
|
|
410
|
+
rejectsDisabledThinking: rejectsDisabledThinking(normalizedId),
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export function deriveCapabilities(
|
|
415
|
+
normalizedId: string,
|
|
416
|
+
profile: ClaudeCodeProtocolProfile = CLAUDE_CODE_2_1_195_PROFILE,
|
|
417
|
+
): ClaudeCodeCapabilities {
|
|
418
|
+
const entry = profile.supportedModels[normalizedId];
|
|
419
|
+
if (entry === undefined) {
|
|
420
|
+
return deriveCapabilitiesFromPredicates(normalizedId);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const capabilities = deriveCapabilitiesFromCatalogue(entry, normalizedId);
|
|
424
|
+
|
|
425
|
+
/*
|
|
426
|
+
* DEMARCATED EXCEPTION -- docs/plans/BLOCKERS.md, finding C1.
|
|
427
|
+
*
|
|
428
|
+
* Exactly one cell of the 2.1.195 catalogue disagrees with the binary that
|
|
429
|
+
* shipped it: `claude-opus-4-5` omits `effort` from its `capabilities`
|
|
430
|
+
* array, but 2.1.195 derives capabilities from predicate code and `Kw`
|
|
431
|
+
* (`supportsEffort`) does not exclude `claude-opus-4-5`. The predicate is
|
|
432
|
+
* therefore wire-authoritative for this profile, and the golden fixtures
|
|
433
|
+
* and packed-consumer digests prove `effort: true` is what 2.1.195 sends.
|
|
434
|
+
*
|
|
435
|
+
* Scope. This exception belongs to the 2.1.195 profile only, and the
|
|
436
|
+
* behaviour guard below enforces that mechanically. Upstream 2.1.222+
|
|
437
|
+
* switches derivation to the catalogue, which makes `effort: false`
|
|
438
|
+
* genuine there: for those profiles the catalogue IS the truth, so a
|
|
439
|
+
* profile ported from them does NOT inherit this block. Which profiles are
|
|
440
|
+
* on which side is `profile-behaviors.ts`'s question, not this module's.
|
|
441
|
+
*/
|
|
442
|
+
if (
|
|
443
|
+
profileBehaviors(profile).opus45EffortException &&
|
|
444
|
+
normalizedId === "claude-opus-4-5"
|
|
445
|
+
) {
|
|
446
|
+
return Object.freeze({
|
|
447
|
+
...capabilities,
|
|
448
|
+
effort: supportsEffort(normalizedId),
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return capabilities;
|
|
453
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
|
|
3
|
+
import type { ClaudeCodeModelFamily } from "./contracts.js";
|
|
4
|
+
|
|
5
|
+
/** Ports upstream `dp` (binary offset 226644497). */
|
|
6
|
+
export function stripModelMarkers(model: string): string {
|
|
7
|
+
return model.replace(/\[(1|2)m\]/gi, "");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Ports upstream `$_` (binary offset 226639025). */
|
|
11
|
+
export function normalizeModelId(model: string): string {
|
|
12
|
+
model = model.toLowerCase();
|
|
13
|
+
if (model.includes("claude-fable-5")) return "claude-fable-5";
|
|
14
|
+
if (model.includes("claude-mythos-5")) return "claude-mythos-5";
|
|
15
|
+
if (model.includes("claude-opus-4-8")) return "claude-opus-4-8";
|
|
16
|
+
if (model.includes("claude-opus-4-7")) return "claude-opus-4-7";
|
|
17
|
+
if (model.includes("claude-opus-4-6")) return "claude-opus-4-6";
|
|
18
|
+
if (model.includes("claude-opus-4-5")) return "claude-opus-4-5";
|
|
19
|
+
if (model.includes("claude-opus-4-1")) return "claude-opus-4-1";
|
|
20
|
+
if (/claude-opus-4(?!-\d(?!\d))/.test(model)) return "claude-opus-4-0";
|
|
21
|
+
if (model.includes("claude-sonnet-4-6")) return "claude-sonnet-4-6";
|
|
22
|
+
if (model.includes("claude-sonnet-4-5")) return "claude-sonnet-4-5";
|
|
23
|
+
if (/claude-sonnet-4(?!-\d(?!\d))/.test(model)) return "claude-sonnet-4-0";
|
|
24
|
+
if (model.includes("claude-haiku-4-5")) return "claude-haiku-4-5";
|
|
25
|
+
if (model.includes("claude-3-7-sonnet")) return "claude-3-7-sonnet";
|
|
26
|
+
if (model.includes("claude-3-5-sonnet")) return "claude-3-5-sonnet";
|
|
27
|
+
if (model.includes("claude-3-5-haiku")) return "claude-3-5-haiku";
|
|
28
|
+
if (model.includes("claude-3-opus")) return "claude-3-opus";
|
|
29
|
+
if (model.includes("claude-3-sonnet")) return "claude-3-sonnet";
|
|
30
|
+
if (model.includes("claude-3-haiku")) return "claude-3-haiku";
|
|
31
|
+
return model.replace(/-\d{8}$/, "");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Derives the package-local family classification used only by
|
|
36
|
+
* `RedactedRequestEvidence`; upstream has no corresponding family concept.
|
|
37
|
+
*/
|
|
38
|
+
export function modelFamilyOf(normalizedId: string): ClaudeCodeModelFamily {
|
|
39
|
+
if (normalizedId.includes("fable")) return "fable";
|
|
40
|
+
if (normalizedId.includes("mythos")) return "mythos";
|
|
41
|
+
if (normalizedId.includes("haiku")) return "haiku";
|
|
42
|
+
if (normalizedId.includes("sonnet")) return "sonnet";
|
|
43
|
+
if (normalizedId.includes("opus")) return "opus";
|
|
44
|
+
return "unknown";
|
|
45
|
+
}
|
package/src/models.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
ClaudeCodeCapabilities,
|
|
5
|
+
ClaudeCodeModelFamily,
|
|
6
|
+
ClaudeCodeProtocolProfile,
|
|
7
|
+
} from "./contracts.js";
|
|
8
|
+
import { ClaudeCodeWireError } from "./contracts.js";
|
|
9
|
+
import { deriveCapabilities } from "./model-capabilities.js";
|
|
10
|
+
import {
|
|
11
|
+
modelFamilyOf,
|
|
12
|
+
normalizeModelId,
|
|
13
|
+
stripModelMarkers,
|
|
14
|
+
} from "./model-identity.js";
|
|
15
|
+
import { CLAUDE_CODE_2_1_195_PROFILE } from "./profiles/claude-code-2.1.195.js";
|
|
16
|
+
|
|
17
|
+
export interface ResolvedClaudeCodeModel {
|
|
18
|
+
readonly id: string;
|
|
19
|
+
readonly wireId: string;
|
|
20
|
+
readonly family: ClaudeCodeModelFamily;
|
|
21
|
+
readonly capabilities: ClaudeCodeCapabilities;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function resolveModel(
|
|
25
|
+
model: string,
|
|
26
|
+
profile: ClaudeCodeProtocolProfile = CLAUDE_CODE_2_1_195_PROFILE,
|
|
27
|
+
): ResolvedClaudeCodeModel {
|
|
28
|
+
if (typeof model !== "string" || model.length === 0) {
|
|
29
|
+
throw new ClaudeCodeWireError("INVALID_INPUT", { model });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const wireId = stripModelMarkers(model);
|
|
33
|
+
const id = normalizeModelId(model);
|
|
34
|
+
const entry = Object.hasOwn(profile.supportedModels, id)
|
|
35
|
+
? profile.supportedModels[id]
|
|
36
|
+
: undefined;
|
|
37
|
+
return Object.freeze({
|
|
38
|
+
id,
|
|
39
|
+
wireId,
|
|
40
|
+
// The catalogue supplies the family here, and -- since T1.1.2 -- also
|
|
41
|
+
// supplies the six catalogue-backed capabilities. Both now honour THIS
|
|
42
|
+
// profile: `deriveCapabilities` takes the active profile, so a request
|
|
43
|
+
// built against a non-pinned profile derives from that profile's
|
|
44
|
+
// catalogue rather than from 2.1.195's. Ids with no catalogue entry fall
|
|
45
|
+
// back to the ported predicates, which are pure functions of the
|
|
46
|
+
// normalized id. See the header of `model-capabilities.ts`.
|
|
47
|
+
family: entry?.family ?? modelFamilyOf(id),
|
|
48
|
+
capabilities: deriveCapabilities(id, profile),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
|
|
3
|
+
import type { ClaudeCodeProtocolProfile } from "./contracts.js";
|
|
4
|
+
import { CLAUDE_CODE_2_1_195_PROFILE } from "./profiles/claude-code-2.1.195.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Per-profile behaviour dispatch.
|
|
8
|
+
*
|
|
9
|
+
* Three modules — `thinking.ts`, `fingerprint.ts` and `model-capabilities.ts` —
|
|
10
|
+
* each need to know whether the profile they were handed behaves like upstream
|
|
11
|
+
* 2.1.195 or like upstream 2.1.222+. Each grew its own
|
|
12
|
+
* `profile.id === CLAUDE_CODE_2_1_195_PROFILE.id` comparison as its feature
|
|
13
|
+
* landed, and three copies of one question is three places to forget when a
|
|
14
|
+
* fourth profile arrives. This module is the single place that asks it.
|
|
15
|
+
*
|
|
16
|
+
* The flags are named for the BEHAVIOUR they gate, never for a version. A call
|
|
17
|
+
* site reading `profileBehaviors(profile).billingChainSegments` says what it is
|
|
18
|
+
* deciding; a call site reading `profile.id !== …195….id` says only which
|
|
19
|
+
* client it is not, and leaves the reader to reconstruct why that matters.
|
|
20
|
+
* `test/governance/version-dispatch.test.ts` fails the build if a fourth copy
|
|
21
|
+
* of the comparison appears in `src/`.
|
|
22
|
+
*
|
|
23
|
+
* `ClaudeCodeProfileBehaviors` deliberately stays here rather than moving to
|
|
24
|
+
* `contracts.ts`. It is not part of the wire contract and is not exported from
|
|
25
|
+
* `index.ts`: it is an internal derivation over a profile, and putting it in
|
|
26
|
+
* the contract module would imply consumers can supply one, which they cannot.
|
|
27
|
+
* Behaviour is derived from the profile, never declared alongside it.
|
|
28
|
+
*/
|
|
29
|
+
export interface ClaudeCodeProfileBehaviors {
|
|
30
|
+
/**
|
|
31
|
+
* Whether a caller's own `max_tokens` of 4096 or more raises the model's
|
|
32
|
+
* `upperLimit` and lowers its `default` to fit under it.
|
|
33
|
+
*
|
|
34
|
+
* Consumed by `modelOutputTokenLimits` in `thinking.ts`, whose one
|
|
35
|
+
* wire-visible effect is the seed of the default thinking budget.
|
|
36
|
+
*/
|
|
37
|
+
readonly requestDerivedTokenCeiling: boolean;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Whether the billing block may carry the `cc_prev_req` and `cc_prompt_id`
|
|
41
|
+
* conversation-chaining segments.
|
|
42
|
+
*
|
|
43
|
+
* Consumed by `createBillingBlock` in `fingerprint.ts`. False means the
|
|
44
|
+
* segments are dropped silently even when the caller supplies both, which is
|
|
45
|
+
* what a client with no parameter for them does.
|
|
46
|
+
*/
|
|
47
|
+
readonly billingChainSegments: boolean;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Whether `claude-opus-4-5` takes its `effort` capability from the predicate
|
|
51
|
+
* rather than from the catalogue.
|
|
52
|
+
*
|
|
53
|
+
* Consumed by `deriveCapabilities` in `model-capabilities.ts`. See
|
|
54
|
+
* `docs/plans/BLOCKERS.md`, finding C1: this is the one cell where the
|
|
55
|
+
* 2.1.195 catalogue disagrees with the binary that shipped it, and the
|
|
56
|
+
* predicate is wire-authoritative for that profile alone.
|
|
57
|
+
*/
|
|
58
|
+
readonly opus45EffortException: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Upstream 2.1.195: derivation is predicate-driven, the request builder has no
|
|
63
|
+
* request-derived token ceiling, and the billing block has no parameter for
|
|
64
|
+
* conversation chaining.
|
|
65
|
+
*/
|
|
66
|
+
const LEGACY_BEHAVIORS: ClaudeCodeProfileBehaviors = Object.freeze({
|
|
67
|
+
requestDerivedTokenCeiling: false,
|
|
68
|
+
billingChainSegments: false,
|
|
69
|
+
opus45EffortException: true,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Upstream 2.1.222 and later: derivation is catalogue-first, so the
|
|
74
|
+
* `claude-opus-4-5` exception does not apply, and both of the newer request
|
|
75
|
+
* behaviours are present.
|
|
76
|
+
*/
|
|
77
|
+
const MODERN_BEHAVIORS: ClaudeCodeProfileBehaviors = Object.freeze({
|
|
78
|
+
requestDerivedTokenCeiling: true,
|
|
79
|
+
billingChainSegments: true,
|
|
80
|
+
opus45EffortException: false,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Resolves the behaviour set a profile follows.
|
|
85
|
+
*
|
|
86
|
+
* Pure and total: every profile answers, and the answer depends on nothing but
|
|
87
|
+
* the profile's identity.
|
|
88
|
+
*/
|
|
89
|
+
export function profileBehaviors(
|
|
90
|
+
profile: ClaudeCodeProtocolProfile,
|
|
91
|
+
): ClaudeCodeProfileBehaviors {
|
|
92
|
+
/*
|
|
93
|
+
* ---- Demarcated: the ONLY per-version identity comparison in `src/`. ----
|
|
94
|
+
*
|
|
95
|
+
* The split is 2.1.195 versus 2.1.222+, and it is structural rather than a
|
|
96
|
+
* capability flag: these are differences in what the upstream builder is
|
|
97
|
+
* written to do, not values it reads from a catalogue. 2.1.195 is the one
|
|
98
|
+
* profile ported from the older builder, so it is the one named here.
|
|
99
|
+
*
|
|
100
|
+
* The default is deliberately the MODERN side. A profile ported from a
|
|
101
|
+
* client newer than 2.1.233 inherits the current behaviour and needs no edit
|
|
102
|
+
* here; only a profile ported from a client OLDER than 2.1.222 would, and
|
|
103
|
+
* adding one is a decision that should require touching this module. Written
|
|
104
|
+
* the other way round — naming the modern profiles and defaulting to legacy
|
|
105
|
+
* — every new profile would silently regress to 2.1.195 semantics.
|
|
106
|
+
*
|
|
107
|
+
* `test/governance/version-dispatch.test.ts` asserts this comparison is
|
|
108
|
+
* here, and that it is nowhere else.
|
|
109
|
+
*/
|
|
110
|
+
if (profile.id === CLAUDE_CODE_2_1_195_PROFILE.id) {
|
|
111
|
+
return LEGACY_BEHAVIORS;
|
|
112
|
+
}
|
|
113
|
+
return MODERN_BEHAVIORS;
|
|
114
|
+
}
|