@tormentalabs/claude-code-wire-compat 0.1.0 → 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 +65 -0
- package/README.md +14 -1
- 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 +9 -2
- package/src/betas.ts +106 -23
- package/src/build-request.ts +155 -11
- package/src/contracts.ts +53 -0
- package/src/fingerprint.ts +78 -8
- package/src/headers.ts +10 -4
- package/src/index.ts +1 -0
- package/src/model-capabilities.ts +171 -13
- package/src/models.ts +8 -4
- 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 +14 -0
- package/src/profiles/claude-code-2.1.233.ts +240 -0
- package/src/redaction.ts +16 -1
- package/src/request-body.ts +16 -9
- package/src/thinking.ts +119 -39
|
@@ -50,32 +50,39 @@ export const CLAUDE_CODE_2_1_195_PROFILE: ClaudeCodeProtocolProfile =
|
|
|
50
50
|
"claude-3-5-haiku": {
|
|
51
51
|
family: "haiku",
|
|
52
52
|
capabilities: [],
|
|
53
|
+
maxOutputTokens: { default: 8192, upper: 8192 },
|
|
53
54
|
},
|
|
54
55
|
"claude-haiku-4-5": {
|
|
55
56
|
family: "haiku",
|
|
56
57
|
capabilities: ["context_management"],
|
|
58
|
+
maxOutputTokens: { default: 32000, upper: 64000 },
|
|
57
59
|
},
|
|
58
60
|
"claude-3-5-sonnet": {
|
|
59
61
|
family: "sonnet",
|
|
60
62
|
capabilities: [],
|
|
63
|
+
maxOutputTokens: { default: 8192, upper: 8192 },
|
|
61
64
|
},
|
|
62
65
|
"claude-3-7-sonnet": {
|
|
63
66
|
family: "sonnet",
|
|
64
67
|
capabilities: [],
|
|
68
|
+
maxOutputTokens: { default: 32000, upper: 64000 },
|
|
65
69
|
},
|
|
66
70
|
"claude-sonnet-4-0": {
|
|
67
71
|
family: "sonnet",
|
|
68
72
|
context: { window: 200000, supports1mBeta: true },
|
|
69
73
|
capabilities: ["context_management"],
|
|
74
|
+
maxOutputTokens: { default: 32000, upper: 64000 },
|
|
70
75
|
},
|
|
71
76
|
"claude-sonnet-4-5": {
|
|
72
77
|
family: "sonnet",
|
|
73
78
|
context: { window: 200000, supports1mBeta: true },
|
|
74
79
|
capabilities: ["context_management"],
|
|
80
|
+
maxOutputTokens: { default: 32000, upper: 64000 },
|
|
75
81
|
},
|
|
76
82
|
"claude-sonnet-4-6": {
|
|
77
83
|
family: "sonnet",
|
|
78
84
|
context: { window: 200000, supports1mBeta: true },
|
|
85
|
+
maxOutputTokens: { default: 32000, upper: 128000 },
|
|
79
86
|
capabilities: [
|
|
80
87
|
"effort",
|
|
81
88
|
"max_effort",
|
|
@@ -86,18 +93,22 @@ export const CLAUDE_CODE_2_1_195_PROFILE: ClaudeCodeProtocolProfile =
|
|
|
86
93
|
"claude-opus-4-0": {
|
|
87
94
|
family: "opus",
|
|
88
95
|
capabilities: ["context_management"],
|
|
96
|
+
maxOutputTokens: { default: 32000, upper: 32000 },
|
|
89
97
|
},
|
|
90
98
|
"claude-opus-4-1": {
|
|
91
99
|
family: "opus",
|
|
92
100
|
capabilities: ["context_management"],
|
|
101
|
+
maxOutputTokens: { default: 32000, upper: 32000 },
|
|
93
102
|
},
|
|
94
103
|
"claude-opus-4-5": {
|
|
95
104
|
family: "opus",
|
|
96
105
|
capabilities: ["context_management"],
|
|
106
|
+
maxOutputTokens: { default: 32000, upper: 64000 },
|
|
97
107
|
},
|
|
98
108
|
"claude-opus-4-6": {
|
|
99
109
|
family: "opus",
|
|
100
110
|
context: { window: 200000, supports1mBeta: true },
|
|
111
|
+
maxOutputTokens: { default: 64000, upper: 128000 },
|
|
101
112
|
capabilities: [
|
|
102
113
|
"effort",
|
|
103
114
|
"max_effort",
|
|
@@ -109,6 +120,7 @@ export const CLAUDE_CODE_2_1_195_PROFILE: ClaudeCodeProtocolProfile =
|
|
|
109
120
|
"claude-opus-4-7": {
|
|
110
121
|
family: "opus",
|
|
111
122
|
context: { window: 1e6, native1m: true, supports1mBeta: true },
|
|
123
|
+
maxOutputTokens: { default: 64000, upper: 128000 },
|
|
112
124
|
capabilities: [
|
|
113
125
|
"effort",
|
|
114
126
|
"max_effort",
|
|
@@ -122,6 +134,7 @@ export const CLAUDE_CODE_2_1_195_PROFILE: ClaudeCodeProtocolProfile =
|
|
|
122
134
|
"claude-opus-4-8": {
|
|
123
135
|
family: "opus",
|
|
124
136
|
context: { window: 1e6, native1m: true, supports1mBeta: true },
|
|
137
|
+
maxOutputTokens: { default: 64000, upper: 128000 },
|
|
125
138
|
capabilities: [
|
|
126
139
|
"effort",
|
|
127
140
|
"max_effort",
|
|
@@ -137,6 +150,7 @@ export const CLAUDE_CODE_2_1_195_PROFILE: ClaudeCodeProtocolProfile =
|
|
|
137
150
|
"claude-fable-5": {
|
|
138
151
|
family: "fable",
|
|
139
152
|
context: { window: 1e6, native1m: true, supports1mBeta: true },
|
|
153
|
+
maxOutputTokens: { default: 64000, upper: 128000 },
|
|
140
154
|
capabilities: [
|
|
141
155
|
"effort",
|
|
142
156
|
"max_effort",
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
|
|
3
|
+
import type { ClaudeCodeProtocolProfile } from "../contracts.js";
|
|
4
|
+
import { COUNT_TOKENS_ENDPOINT } from "../count-tokens.js";
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* Protocol profile for genuine client 2.1.233.
|
|
8
|
+
*
|
|
9
|
+
* Provenance: extracted from the official 2.1.233 win32-x64 binary. The
|
|
10
|
+
* catalogue and every scalar below are transcribed from
|
|
11
|
+
* `docs/protocol/versions/claude-code-2.1.233-analysis.md`, which records the
|
|
12
|
+
* byte offsets they came from.
|
|
13
|
+
*
|
|
14
|
+
* `effort_cost_index` -- the only static-catalogue field added between 2.1.222
|
|
15
|
+
* and 2.1.233 -- is deliberately omitted; the decision is recorded in
|
|
16
|
+
* `docs/source-trace.md`.
|
|
17
|
+
*
|
|
18
|
+
* Context modelling follows 2.1.195 exactly wherever the upstream flags are
|
|
19
|
+
* the same. `supports_1m_suffix` is not modelled, so an entry declaring a
|
|
20
|
+
* 200000 window with nothing but that flag carries no `context` object here,
|
|
21
|
+
* as in the 2.1.195 profile.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
function deepFreeze<T>(value: T): T {
|
|
25
|
+
if (value !== null && typeof value === "object") {
|
|
26
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
27
|
+
deepFreeze(Reflect.get(value, key));
|
|
28
|
+
}
|
|
29
|
+
Object.freeze(value);
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const CLAUDE_CODE_2_1_233_PROFILE: ClaudeCodeProtocolProfile =
|
|
35
|
+
deepFreeze({
|
|
36
|
+
id: "claude-code-2.1.233-sdk-0.112.1",
|
|
37
|
+
cliVersion: "2.1.233",
|
|
38
|
+
sdkVersion: "0.112.1",
|
|
39
|
+
endpoint: "https://api.anthropic.com/v1/messages?beta=true",
|
|
40
|
+
countTokensEndpoint: COUNT_TOKENS_ENDPOINT,
|
|
41
|
+
entrypoint: "cli",
|
|
42
|
+
userAgent: "claude-cli/2.1.233 (external, cli)",
|
|
43
|
+
buildTime: "2026-08-14T17:21:48Z",
|
|
44
|
+
gitSha: "f8d57569aaf350fe25dc4dfa10cad59db8ea4d45",
|
|
45
|
+
attributionHeaderEnabled: true,
|
|
46
|
+
provider: "anthropic",
|
|
47
|
+
anthropicVersion: "2023-06-01",
|
|
48
|
+
contextHintEnabled: false,
|
|
49
|
+
betaPolicy: {
|
|
50
|
+
oauthAuthenticated: true,
|
|
51
|
+
experimentalBetasEnabled: true,
|
|
52
|
+
oneMillionContextEnabled: true,
|
|
53
|
+
interleavedThinkingEnabled: true,
|
|
54
|
+
interactive: true,
|
|
55
|
+
thinkingSummariesShown: false,
|
|
56
|
+
thinkingTokenCountEnabled: true,
|
|
57
|
+
// Inert for this profile: 2.1.233 removed `narration_summaries` from the
|
|
58
|
+
// beta registry, so there is no entry for this flag to gate.
|
|
59
|
+
narrationSummariesEnabled: false,
|
|
60
|
+
structuredOutputsEnabled: false,
|
|
61
|
+
afkModeEnabled: false,
|
|
62
|
+
cacheDiagnosisEnabled: false,
|
|
63
|
+
},
|
|
64
|
+
/**
|
|
65
|
+
* Verbatim genuine-client catalogue, 17 entries in the catalogue's own
|
|
66
|
+
* order. `defaultEffort` is policy exposed as catalogue data; this package
|
|
67
|
+
* must never apply it to a request.
|
|
68
|
+
*/
|
|
69
|
+
supportedModels: {
|
|
70
|
+
"claude-3-5-haiku": {
|
|
71
|
+
family: "haiku",
|
|
72
|
+
capabilities: [],
|
|
73
|
+
maxOutputTokens: { default: 8192, upper: 8192 },
|
|
74
|
+
},
|
|
75
|
+
"claude-haiku-4-5": {
|
|
76
|
+
family: "haiku",
|
|
77
|
+
capabilities: ["context_management"],
|
|
78
|
+
maxOutputTokens: { default: 32000, upper: 64000 },
|
|
79
|
+
},
|
|
80
|
+
"claude-3-5-sonnet": {
|
|
81
|
+
family: "sonnet",
|
|
82
|
+
capabilities: [],
|
|
83
|
+
maxOutputTokens: { default: 8192, upper: 8192 },
|
|
84
|
+
},
|
|
85
|
+
"claude-3-7-sonnet": {
|
|
86
|
+
family: "sonnet",
|
|
87
|
+
capabilities: [],
|
|
88
|
+
maxOutputTokens: { default: 32000, upper: 64000 },
|
|
89
|
+
},
|
|
90
|
+
"claude-sonnet-4-0": {
|
|
91
|
+
family: "sonnet",
|
|
92
|
+
context: { window: 200000, supports1mBeta: true },
|
|
93
|
+
capabilities: ["context_management"],
|
|
94
|
+
maxOutputTokens: { default: 32000, upper: 64000 },
|
|
95
|
+
},
|
|
96
|
+
"claude-sonnet-4-5": {
|
|
97
|
+
family: "sonnet",
|
|
98
|
+
context: { window: 200000, supports1mBeta: true },
|
|
99
|
+
capabilities: ["context_management"],
|
|
100
|
+
maxOutputTokens: { default: 32000, upper: 64000 },
|
|
101
|
+
},
|
|
102
|
+
"claude-sonnet-4-6": {
|
|
103
|
+
family: "sonnet",
|
|
104
|
+
context: { window: 200000, supports1mBeta: true },
|
|
105
|
+
maxOutputTokens: { default: 32000, upper: 128000 },
|
|
106
|
+
capabilities: [
|
|
107
|
+
"effort",
|
|
108
|
+
"max_effort",
|
|
109
|
+
"adaptive_thinking",
|
|
110
|
+
"context_management",
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
// New at 2.1.233. The entry also declares `native_1m_3p` for
|
|
114
|
+
// bedrock/vertex/foundry; this package is anthropic-only, so that flag
|
|
115
|
+
// is not modelled.
|
|
116
|
+
"claude-sonnet-5": {
|
|
117
|
+
family: "sonnet",
|
|
118
|
+
context: { window: 1e6, native1m: true, supports1mBeta: true },
|
|
119
|
+
maxOutputTokens: { default: 64000, upper: 128000 },
|
|
120
|
+
capabilities: [
|
|
121
|
+
"effort",
|
|
122
|
+
"max_effort",
|
|
123
|
+
"xhigh_effort",
|
|
124
|
+
"adaptive_thinking",
|
|
125
|
+
"mid_conv_system",
|
|
126
|
+
"context_management",
|
|
127
|
+
],
|
|
128
|
+
defaultEffort: "high",
|
|
129
|
+
},
|
|
130
|
+
"claude-opus-4-0": {
|
|
131
|
+
family: "opus",
|
|
132
|
+
capabilities: ["context_management"],
|
|
133
|
+
maxOutputTokens: { default: 32000, upper: 32000 },
|
|
134
|
+
},
|
|
135
|
+
"claude-opus-4-1": {
|
|
136
|
+
family: "opus",
|
|
137
|
+
capabilities: ["context_management"],
|
|
138
|
+
maxOutputTokens: { default: 32000, upper: 32000 },
|
|
139
|
+
},
|
|
140
|
+
// No `effort`, and no exception restoring it. The C1 exception in
|
|
141
|
+
// `model-capabilities.ts` belongs to 2.1.195, whose derivation ran off
|
|
142
|
+
// predicates; 2.1.233 derives from this catalogue, so the omission is
|
|
143
|
+
// the genuine answer here.
|
|
144
|
+
"claude-opus-4-5": {
|
|
145
|
+
family: "opus",
|
|
146
|
+
capabilities: ["context_management"],
|
|
147
|
+
maxOutputTokens: { default: 32000, upper: 64000 },
|
|
148
|
+
},
|
|
149
|
+
// Delta D3 vs 2.1.195: `fast_mode` is gone from this entry.
|
|
150
|
+
"claude-opus-4-6": {
|
|
151
|
+
family: "opus",
|
|
152
|
+
context: { window: 200000, supports1mBeta: true },
|
|
153
|
+
maxOutputTokens: { default: 64000, upper: 128000 },
|
|
154
|
+
capabilities: [
|
|
155
|
+
"effort",
|
|
156
|
+
"max_effort",
|
|
157
|
+
"adaptive_thinking",
|
|
158
|
+
"context_management",
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
// Delta D3 vs 2.1.195: `fast_mode` is gone from this entry too.
|
|
162
|
+
"claude-opus-4-7": {
|
|
163
|
+
family: "opus",
|
|
164
|
+
context: { window: 1e6, native1m: true, supports1mBeta: true },
|
|
165
|
+
maxOutputTokens: { default: 64000, upper: 128000 },
|
|
166
|
+
capabilities: [
|
|
167
|
+
"effort",
|
|
168
|
+
"max_effort",
|
|
169
|
+
"xhigh_effort",
|
|
170
|
+
"adaptive_thinking",
|
|
171
|
+
"context_management",
|
|
172
|
+
],
|
|
173
|
+
defaultEffort: "xhigh",
|
|
174
|
+
},
|
|
175
|
+
"claude-opus-4-8": {
|
|
176
|
+
family: "opus",
|
|
177
|
+
context: { window: 1e6, native1m: true, supports1mBeta: true },
|
|
178
|
+
maxOutputTokens: { default: 64000, upper: 128000 },
|
|
179
|
+
capabilities: [
|
|
180
|
+
"effort",
|
|
181
|
+
"max_effort",
|
|
182
|
+
"xhigh_effort",
|
|
183
|
+
"adaptive_thinking",
|
|
184
|
+
"mid_conv_system",
|
|
185
|
+
"context_management",
|
|
186
|
+
"fast_mode",
|
|
187
|
+
"lean_prompt",
|
|
188
|
+
],
|
|
189
|
+
defaultEffort: "high",
|
|
190
|
+
},
|
|
191
|
+
// New at 2.1.233.
|
|
192
|
+
"claude-opus-5": {
|
|
193
|
+
family: "opus",
|
|
194
|
+
context: { window: 1e6, native1m: true, supports1mBeta: true },
|
|
195
|
+
maxOutputTokens: { default: 64000, upper: 128000 },
|
|
196
|
+
capabilities: [
|
|
197
|
+
"effort",
|
|
198
|
+
"max_effort",
|
|
199
|
+
"xhigh_effort",
|
|
200
|
+
"adaptive_thinking",
|
|
201
|
+
"mid_conv_system",
|
|
202
|
+
"context_management",
|
|
203
|
+
"fast_mode",
|
|
204
|
+
"lean_prompt",
|
|
205
|
+
"refusal_fallback",
|
|
206
|
+
"opus_5_prompt_bundle",
|
|
207
|
+
],
|
|
208
|
+
defaultEffort: "high",
|
|
209
|
+
},
|
|
210
|
+
"claude-fable-5": {
|
|
211
|
+
family: "fable",
|
|
212
|
+
context: { window: 1e6, native1m: true, supports1mBeta: true },
|
|
213
|
+
maxOutputTokens: { default: 64000, upper: 128000 },
|
|
214
|
+
capabilities: [
|
|
215
|
+
"effort",
|
|
216
|
+
"max_effort",
|
|
217
|
+
"xhigh_effort",
|
|
218
|
+
"adaptive_thinking",
|
|
219
|
+
"rejects_disabled_thinking",
|
|
220
|
+
"mid_conv_system",
|
|
221
|
+
"context_management",
|
|
222
|
+
"lean_prompt",
|
|
223
|
+
"fable_5_mitigations",
|
|
224
|
+
"refusal_fallback",
|
|
225
|
+
],
|
|
226
|
+
defaultEffort: "high",
|
|
227
|
+
},
|
|
228
|
+
// New at 2.1.233, and the notable one: under 2.1.195 `claude-mythos-5`
|
|
229
|
+
// had no catalogue entry at all (product decision D-1) and was
|
|
230
|
+
// recognised only by name in individual predicates. Here it is
|
|
231
|
+
// catalogued with an empty capability array, which is a denial rather
|
|
232
|
+
// than the maximally permissive predicate fallback.
|
|
233
|
+
"claude-mythos-5": {
|
|
234
|
+
family: "mythos",
|
|
235
|
+
context: { window: 1e6, native1m: true, supports1mBeta: true },
|
|
236
|
+
capabilities: [],
|
|
237
|
+
maxOutputTokens: { default: 64000, upper: 128000 },
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
});
|
package/src/redaction.ts
CHANGED
|
@@ -65,6 +65,17 @@ export interface BuildRedactedEvidenceInput {
|
|
|
65
65
|
const MAX_INPUT_DEPTH = 100;
|
|
66
66
|
const MAX_INPUT_SIZE = 1_000_000;
|
|
67
67
|
const ENDPOINT = "https://api.anthropic.com/v1/messages?beta=true";
|
|
68
|
+
/**
|
|
69
|
+
* The profile ids evidence may claim, spelled out here for the same reason
|
|
70
|
+
* `ENDPOINT` is: this module validates UNTRUSTED evidence records, so it
|
|
71
|
+
* compares them against literals of its own rather than importing the
|
|
72
|
+
* profiles it is checking. Membership stays exact -- an id outside this set
|
|
73
|
+
* is rejected, as a free-form id always was.
|
|
74
|
+
*/
|
|
75
|
+
const PINNED_PROFILE_IDS: ReadonlySet<string> = new Set([
|
|
76
|
+
"claude-code-2.1.195-sdk-0.94.0",
|
|
77
|
+
"claude-code-2.1.233-sdk-0.112.1",
|
|
78
|
+
]);
|
|
68
79
|
const FORBIDDEN_KEYS = new Set(["__proto__", "prototype", "constructor"]);
|
|
69
80
|
const SAFE_ERROR_CODES = new Set([
|
|
70
81
|
"INVALID_INPUT",
|
|
@@ -285,7 +296,11 @@ function assertEvidenceSources(value: unknown): void {
|
|
|
285
296
|
const logicalHeaders = readOwnValue(value, "logicalHeaders");
|
|
286
297
|
const betaFeatures = readOwnValue(value, "betaFeatures");
|
|
287
298
|
|
|
288
|
-
if (
|
|
299
|
+
if (
|
|
300
|
+
typeof profileId !== "string" ||
|
|
301
|
+
!PINNED_PROFILE_IDS.has(profileId) ||
|
|
302
|
+
endpoint !== ENDPOINT
|
|
303
|
+
) {
|
|
289
304
|
throw wireError("INVALID_INPUT");
|
|
290
305
|
}
|
|
291
306
|
if (effectiveProfile !== undefined) {
|
package/src/request-body.ts
CHANGED
|
@@ -1413,22 +1413,26 @@ function capabilityBoolean(value: unknown, fallback: boolean): boolean {
|
|
|
1413
1413
|
return value;
|
|
1414
1414
|
}
|
|
1415
1415
|
|
|
1416
|
-
function modelResolution(
|
|
1416
|
+
function modelResolution(
|
|
1417
|
+
value: unknown,
|
|
1418
|
+
profile: ClaudeCodeProtocolProfile = CLAUDE_CODE_2_1_195_PROFILE,
|
|
1419
|
+
): ModelResolution {
|
|
1417
1420
|
const record = requireRecord(value);
|
|
1418
1421
|
const capabilityValue = record["capabilities"];
|
|
1419
|
-
// A catalogue-shaped capability array is accepted at this boundary, but
|
|
1420
|
-
// never
|
|
1421
|
-
// the
|
|
1422
|
-
// are still validated so that
|
|
1422
|
+
// A catalogue-shaped capability array is accepted at this boundary, but its
|
|
1423
|
+
// elements never drive derivation: the active profile's catalogue does,
|
|
1424
|
+
// falling back to the id predicates. See the header of
|
|
1425
|
+
// `model-capabilities.ts`. The elements are still validated so that
|
|
1426
|
+
// malformed input fails closed.
|
|
1423
1427
|
if (Array.isArray(capabilityValue)) {
|
|
1424
1428
|
for (const capability of capabilityValue) {
|
|
1425
1429
|
if (typeof capability !== "string") fail("INVALID_INPUT");
|
|
1426
1430
|
}
|
|
1427
1431
|
}
|
|
1428
1432
|
const capabilities = Array.isArray(capabilityValue)
|
|
1429
|
-
? deriveCapabilities(String(record["id"]))
|
|
1433
|
+
? deriveCapabilities(String(record["id"]), profile)
|
|
1430
1434
|
: requireRecord(capabilityValue);
|
|
1431
|
-
const derived = deriveCapabilities(String(record["id"]));
|
|
1435
|
+
const derived = deriveCapabilities(String(record["id"]), profile);
|
|
1432
1436
|
if (
|
|
1433
1437
|
(capabilities.thinking !== undefined &&
|
|
1434
1438
|
typeof capabilities.thinking !== "boolean") ||
|
|
@@ -1739,7 +1743,8 @@ export function buildCanonicalBody(
|
|
|
1739
1743
|
const input = requireRecord(rawInput);
|
|
1740
1744
|
assertExactKeys(input, INPUT_KEY_SET);
|
|
1741
1745
|
requireKeys(input, ["maxTokens", "messages"]);
|
|
1742
|
-
const
|
|
1746
|
+
const effectiveProfile = profile ?? CLAUDE_CODE_2_1_195_PROFILE;
|
|
1747
|
+
const resolvedModel = modelResolution(rawResolvedModel, effectiveProfile);
|
|
1743
1748
|
if (
|
|
1744
1749
|
hasOwn(input, "model") &&
|
|
1745
1750
|
(typeof input["model"] !== "string" ||
|
|
@@ -1775,6 +1780,7 @@ export function buildCanonicalBody(
|
|
|
1775
1780
|
const maxTokens = clampMaxTokens(
|
|
1776
1781
|
requirePositiveInteger(input["maxTokens"]),
|
|
1777
1782
|
resolvedModel.id,
|
|
1783
|
+
effectiveProfile,
|
|
1778
1784
|
);
|
|
1779
1785
|
const result: Record<string, unknown> = {
|
|
1780
1786
|
model: resolvedModel.wireId,
|
|
@@ -1828,8 +1834,9 @@ export function buildCanonicalBody(
|
|
|
1828
1834
|
thinkingRequest,
|
|
1829
1835
|
resolvedModel.id,
|
|
1830
1836
|
resolvedModel.capabilities,
|
|
1831
|
-
|
|
1837
|
+
effectiveProfile.betaPolicy,
|
|
1832
1838
|
maxTokens,
|
|
1839
|
+
effectiveProfile,
|
|
1833
1840
|
);
|
|
1834
1841
|
if (resolved.emitted !== undefined) result["thinking"] = resolved.emitted;
|
|
1835
1842
|
|
package/src/thinking.ts
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
import type {
|
|
4
4
|
ClaudeCodeBetaPolicy,
|
|
5
5
|
ClaudeCodeCapabilities,
|
|
6
|
+
ClaudeCodeProtocolProfile,
|
|
6
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";
|
|
7
10
|
|
|
8
11
|
/**
|
|
9
12
|
* Extended-thinking resolution, ported from the genuine client's request
|
|
@@ -73,9 +76,21 @@ export interface ResolvedThinking {
|
|
|
73
76
|
|
|
74
77
|
/**
|
|
75
78
|
* Per-model output token limits, ported from upstream `Xxe` at byte offset
|
|
76
|
-
* 227378240. Keyed on the NORMALISED model id
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
+
* 227378240. Keyed on the NORMALISED model id.
|
|
80
|
+
*
|
|
81
|
+
* Resolution order, since Fase 1.2:
|
|
82
|
+
*
|
|
83
|
+
* 1. The pinned 2.1.195 catalogue, when the id has an entry carrying
|
|
84
|
+
* `maxOutputTokens`. That is the single source of truth for every model
|
|
85
|
+
* the profile knows, and `token-limits-equivalence.test.ts` pins the two
|
|
86
|
+
* sources cell by cell.
|
|
87
|
+
* 2. Otherwise the transcribed `Xxe` table below, preserved intact as the
|
|
88
|
+
* demarcated fallback. It is NOT dead code and must not be trimmed to
|
|
89
|
+
* "only the ids the catalogue lacks": `claude-3-opus`, `claude-3-sonnet`
|
|
90
|
+
* and `claude-3-haiku` are reachable through the normaliser with no
|
|
91
|
+
* catalogue entry, `claude-mythos-5` is absent from the catalogue by
|
|
92
|
+
* product decision D-1, and any id from a newer client lands on the
|
|
93
|
+
* final fallback row.
|
|
79
94
|
*
|
|
80
95
|
* Both fields are load-bearing. `upperLimit` seeds the thinking budget when the
|
|
81
96
|
* caller supplies none (upstream `wvi = Xxe(e).upperLimit - 1`); `default` caps
|
|
@@ -89,38 +104,96 @@ export interface ResolvedThinking {
|
|
|
89
104
|
* override this package cannot observe.
|
|
90
105
|
* - `bvi(e)` adjusts BOTH fields, but sits behind `_vi()`, which returns a
|
|
91
106
|
* hard `false`. Dead code upstream.
|
|
107
|
+
*
|
|
108
|
+
* From 2.1.222 onward upstream grew a THIRD adjustment, this one derived from
|
|
109
|
+
* the request rather than from host state, and therefore observable: see
|
|
110
|
+
* `requestedMaxTokens` below.
|
|
111
|
+
*
|
|
112
|
+
* @param requestedMaxTokens
|
|
113
|
+
* The caller's own `max_tokens`, when the call site has it. Modelled for
|
|
114
|
+
* profiles from 2.1.222 onward only; see the demarcated block below.
|
|
92
115
|
*/
|
|
93
116
|
export function modelOutputTokenLimits(
|
|
94
117
|
normalizedId: string,
|
|
118
|
+
profile: ClaudeCodeProtocolProfile = CLAUDE_CODE_2_1_195_PROFILE,
|
|
119
|
+
requestedMaxTokens?: number,
|
|
95
120
|
): ModelOutputTokenLimits {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
121
|
+
const resolved = resolveDeclaredLimits(normalizedId, profile);
|
|
122
|
+
|
|
123
|
+
/*
|
|
124
|
+
* ---- Demarcated: request-derived upper bound, upstream 2.1.222+. ----
|
|
125
|
+
*
|
|
126
|
+
* Upstream raises `upperLimit` to the caller's own `max_tokens` and lowers
|
|
127
|
+
* `default` to fit under it:
|
|
128
|
+
*
|
|
129
|
+
* upperLimit = requestedMaxTokens;
|
|
130
|
+
* default = Math.min(default, upperLimit);
|
|
131
|
+
*
|
|
132
|
+
* Verified byte-identical between upstream 2.1.222 and 2.1.233.
|
|
133
|
+
*
|
|
134
|
+
* The gate is STRUCTURAL, not a capability flag: this behaviour exists in
|
|
135
|
+
* upstream 2.1.222+ and 2.1.195 does not have it, so the 195 profile must
|
|
136
|
+
* never see it. Which profiles are on which side is `profile-behaviors.ts`'s
|
|
137
|
+
* question, not this module's.
|
|
138
|
+
*
|
|
139
|
+
* `Number.isSafeInteger` is deliberately stricter than upstream's truthy
|
|
140
|
+
* check. The upstream runtime only ever produces integers in this field, so
|
|
141
|
+
* the two agree on every reachable input; here a NaN or Infinity would
|
|
142
|
+
* propagate straight into `budget_tokens`, which must stay an integer.
|
|
143
|
+
*/
|
|
111
144
|
if (
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
145
|
+
profileBehaviors(profile).requestDerivedTokenCeiling &&
|
|
146
|
+
requestedMaxTokens !== undefined &&
|
|
147
|
+
Number.isSafeInteger(requestedMaxTokens) &&
|
|
148
|
+
requestedMaxTokens >= 4096
|
|
116
149
|
) {
|
|
117
|
-
|
|
150
|
+
const upperLimit = requestedMaxTokens;
|
|
151
|
+
return { default: Math.min(resolved.default, upperLimit), upperLimit };
|
|
118
152
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
153
|
+
|
|
154
|
+
return resolved;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function resolveDeclaredLimits(
|
|
158
|
+
normalizedId: string,
|
|
159
|
+
profile: ClaudeCodeProtocolProfile,
|
|
160
|
+
): ModelOutputTokenLimits {
|
|
161
|
+
const declared = profile.supportedModels[normalizedId]?.maxOutputTokens;
|
|
162
|
+
if (declared !== undefined) {
|
|
163
|
+
// `upper` is the catalogue's name for what this module calls `upperLimit`;
|
|
164
|
+
// the rename happens here and nowhere else.
|
|
165
|
+
return { default: declared.default, upperLimit: declared.upper };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/*
|
|
169
|
+
* ---- Demarcated fallback: the reachable remainder of `Xxe`. ----
|
|
170
|
+
*
|
|
171
|
+
* No catalogue id reaches this point. Every entry of the 2.1.195 catalogue
|
|
172
|
+
* declares `maxOutputTokens`, and `capability-equivalence.test.ts` fails if
|
|
173
|
+
* one stops doing so, which is what keeps the rows below to the ids the
|
|
174
|
+
* catalogue genuinely cannot answer for:
|
|
175
|
+
*
|
|
176
|
+
* - `claude-mythos-5` has no catalogue entry by product decision D-1.
|
|
177
|
+
* - `claude-3-opus`, `claude-3-sonnet` and `claude-3-haiku` are reachable
|
|
178
|
+
* through the normaliser and predate the catalogue.
|
|
179
|
+
*
|
|
180
|
+
* The rows for catalogued ids were deleted rather than kept "just in case":
|
|
181
|
+
* they were unreachable, so they could be neither covered nor
|
|
182
|
+
* mutation-killed, and a second copy of a limit that no longer serves any
|
|
183
|
+
* request is exactly the duplicated table
|
|
184
|
+
* `test/governance/single-source-of-truth.test.ts` exists to prevent.
|
|
185
|
+
*
|
|
186
|
+
* This mirrors upstream 2.1.222, where derivation is catalogue-first and
|
|
187
|
+
* the surviving legacy rows are the `claude-3-*` ones plus a generic tail.
|
|
188
|
+
*
|
|
189
|
+
* If a future profile omits `maxOutputTokens` for some id, that id lands on
|
|
190
|
+
* the generic tail below -- 32000/128000 -- rather than on a stale
|
|
191
|
+
* per-model row. That is deliberate: a wrong-but-loud generic limit is
|
|
192
|
+
* recoverable, a silently stale per-model limit is not. The equivalence
|
|
193
|
+
* guard fires first in any case.
|
|
194
|
+
*/
|
|
195
|
+
if (normalizedId === "claude-mythos-5") {
|
|
196
|
+
return { default: 64000, upperLimit: 128000 };
|
|
124
197
|
}
|
|
125
198
|
if (normalizedId === "claude-3-opus") {
|
|
126
199
|
return { default: 4096, upperLimit: 4096 };
|
|
@@ -131,15 +204,6 @@ export function modelOutputTokenLimits(
|
|
|
131
204
|
if (normalizedId === "claude-3-haiku") {
|
|
132
205
|
return { default: 4096, upperLimit: 4096 };
|
|
133
206
|
}
|
|
134
|
-
if (
|
|
135
|
-
normalizedId === "claude-3-5-sonnet" ||
|
|
136
|
-
normalizedId === "claude-3-5-haiku"
|
|
137
|
-
) {
|
|
138
|
-
return { default: 8192, upperLimit: 8192 };
|
|
139
|
-
}
|
|
140
|
-
if (normalizedId === "claude-3-7-sonnet") {
|
|
141
|
-
return { default: 32000, upperLimit: 64000 };
|
|
142
|
-
}
|
|
143
207
|
return { default: 32000, upperLimit: 128000 };
|
|
144
208
|
}
|
|
145
209
|
|
|
@@ -162,12 +226,22 @@ export function modelOutputTokenLimits(
|
|
|
162
226
|
* Upstream uses `||`, not `??`, so a zero override would fall back to the
|
|
163
227
|
* default. Unreachable here: `max_tokens` is validated as a positive integer
|
|
164
228
|
* before this runs.
|
|
229
|
+
*
|
|
230
|
+
* `requested` is forwarded as the request-derived bound so that this call site
|
|
231
|
+
* reads the same table upstream reads. It cannot change the result: the
|
|
232
|
+
* override only ever lowers `default` to `requested`, and
|
|
233
|
+
* `min(requested, min(default, requested)) === min(requested, default)`. It is
|
|
234
|
+
* passed for coherence of reading, not for effect.
|
|
165
235
|
*/
|
|
166
236
|
export function clampMaxTokens(
|
|
167
237
|
requested: number,
|
|
168
238
|
normalizedId: string,
|
|
239
|
+
profile: ClaudeCodeProtocolProfile = CLAUDE_CODE_2_1_195_PROFILE,
|
|
169
240
|
): number {
|
|
170
|
-
return Math.min(
|
|
241
|
+
return Math.min(
|
|
242
|
+
requested,
|
|
243
|
+
modelOutputTokenLimits(normalizedId, profile, requested).default,
|
|
244
|
+
);
|
|
171
245
|
}
|
|
172
246
|
|
|
173
247
|
/**
|
|
@@ -216,6 +290,7 @@ export function resolveThinking(
|
|
|
216
290
|
capabilities: ClaudeCodeCapabilities,
|
|
217
291
|
betaPolicy: ClaudeCodeBetaPolicy,
|
|
218
292
|
maxTokens: number,
|
|
293
|
+
profile: ClaudeCodeProtocolProfile = CLAUDE_CODE_2_1_195_PROFILE,
|
|
219
294
|
): ResolvedThinking {
|
|
220
295
|
// Upstream `nr = n.type !== "disabled" && !CLAUDE_CODE_DISABLE_THINKING`.
|
|
221
296
|
const requestActive = request !== undefined && request.type !== "disabled";
|
|
@@ -236,9 +311,14 @@ export function resolveThinking(
|
|
|
236
311
|
// Upstream: `let Tr = wvi(u)` — the model's upper limit minus one —
|
|
237
312
|
// overridden by the caller's budget when supplied, then clamped by
|
|
238
313
|
// `Tr = Math.min(Fi - 1, Tr)` where `Fi` is the emitted `max_tokens`.
|
|
314
|
+
//
|
|
315
|
+
// This is the one wire-visible consumer of the request-derived bound: on
|
|
316
|
+
// a 2.1.222+ profile a caller asking for a `max_tokens` above the
|
|
317
|
+
// catalogue's upper limit seeds the default budget from THEIR number
|
|
318
|
+
// minus one, not from the catalogue's.
|
|
239
319
|
const requested =
|
|
240
320
|
request.budgetTokens ??
|
|
241
|
-
modelOutputTokenLimits(normalizedId).upperLimit - 1;
|
|
321
|
+
modelOutputTokenLimits(normalizedId, profile, maxTokens).upperLimit - 1;
|
|
242
322
|
emitted = { budget_tokens: Math.min(maxTokens - 1, requested) };
|
|
243
323
|
emitted["type"] = "enabled";
|
|
244
324
|
if (display !== undefined) emitted["display"] = display;
|