@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
package/src/betas.ts
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
|
|
3
|
+
import { BETA_REGISTRY } from "./beta-registry.js";
|
|
4
|
+
import type {
|
|
5
|
+
ClaudeCodeCapabilities,
|
|
6
|
+
ClaudeCodeProtocolProfile,
|
|
7
|
+
} from "./contracts.js";
|
|
8
|
+
import { ClaudeCodeWireError } from "./contracts.js";
|
|
9
|
+
import {
|
|
10
|
+
supportsMidConversationSystem,
|
|
11
|
+
supportsStructuredOutputs,
|
|
12
|
+
} from "./model-capabilities.js";
|
|
13
|
+
import { BETA_REGISTRY_2_1_233 } from "./profiles/beta-registry-2.1.233.js";
|
|
14
|
+
import { CLAUDE_CODE_2_1_195_PROFILE } from "./profiles/claude-code-2.1.195.js";
|
|
15
|
+
import { CLAUDE_CODE_2_1_233_PROFILE } from "./profiles/claude-code-2.1.233.js";
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
* Provenance. The emitted order is a port of the genuine client's base beta set
|
|
19
|
+
* `$9r` (byte offset 227387921), followed by the gated pushes the request
|
|
20
|
+
* builder performs afterwards (bytes 238153535-238155672). The identifiers
|
|
21
|
+
* themselves come from registry `Udd`, ported verbatim as `BETA_REGISTRY`.
|
|
22
|
+
*
|
|
23
|
+
* The order is EMERGENT, not declared: upstream neither sorts the array nor
|
|
24
|
+
* keeps a canonical list, so the sequence below is load-bearing and must not be
|
|
25
|
+
* reordered for tidiness. `docs/source-trace.md` records the same fact under
|
|
26
|
+
* "Beta registry and push order".
|
|
27
|
+
*
|
|
28
|
+
* The push SITES are shared across profiles; the identifiers they push are not.
|
|
29
|
+
* A profile therefore selects its registry (see `resolveBetaRegistry`) and the
|
|
30
|
+
* sequence of sites stays fixed, which is what keeps a registry change from
|
|
31
|
+
* silently becoming an ordering change.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/** Structural shape of a registry entry, shared by every registry version. */
|
|
35
|
+
interface BetaRegistryEntry {
|
|
36
|
+
readonly featureKey: string;
|
|
37
|
+
readonly header: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The entries the push sites below require, as a structural contract rather
|
|
42
|
+
* than a reference to one concrete registry.
|
|
43
|
+
*
|
|
44
|
+
* Registry versions have different key sets. Every key here except
|
|
45
|
+
* `NARRATION_SUMMARIES` is present in all of them, so those push sites index
|
|
46
|
+
* directly. `NARRATION_SUMMARIES` is optional because upstream removed it after
|
|
47
|
+
* 2.1.195 (see `src/profiles/beta-registry-2.1.233.ts`): its push site survives
|
|
48
|
+
* and becomes inert when the resolved registry has no entry to push. Making the
|
|
49
|
+
* optionality part of the TYPE is what forces every future registry to be
|
|
50
|
+
* checked against the push sites at compile time instead of at runtime.
|
|
51
|
+
*/
|
|
52
|
+
export interface ComposableBetaRegistry {
|
|
53
|
+
readonly CLAUDE_CODE: BetaRegistryEntry;
|
|
54
|
+
readonly OAUTH_AUTH: BetaRegistryEntry;
|
|
55
|
+
readonly LONG_CONTEXT: BetaRegistryEntry;
|
|
56
|
+
readonly INTERLEAVED_THINKING: BetaRegistryEntry;
|
|
57
|
+
readonly REDACT_THINKING: BetaRegistryEntry;
|
|
58
|
+
readonly THINKING_TOKEN_COUNT: BetaRegistryEntry;
|
|
59
|
+
readonly CONTEXT_MANAGEMENT: BetaRegistryEntry;
|
|
60
|
+
readonly STRUCTURED_OUTPUTS: BetaRegistryEntry;
|
|
61
|
+
readonly PROMPT_CACHING_SCOPE: BetaRegistryEntry;
|
|
62
|
+
readonly MID_CONVERSATION_SYSTEM: BetaRegistryEntry;
|
|
63
|
+
readonly EFFORT: BetaRegistryEntry;
|
|
64
|
+
readonly SPEED: BetaRegistryEntry;
|
|
65
|
+
readonly AFK_MODE: BetaRegistryEntry;
|
|
66
|
+
readonly EXTENDED_CACHE_TTL: BetaRegistryEntry;
|
|
67
|
+
readonly CONTEXT_HINT: BetaRegistryEntry;
|
|
68
|
+
readonly CACHE_DIAGNOSIS: BetaRegistryEntry;
|
|
69
|
+
readonly NARRATION_SUMMARIES?: BetaRegistryEntry;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const PROFILE_BETA_REGISTRIES: ReadonlyMap<string, ComposableBetaRegistry> =
|
|
73
|
+
new Map<string, ComposableBetaRegistry>([
|
|
74
|
+
[CLAUDE_CODE_2_1_195_PROFILE.id, BETA_REGISTRY],
|
|
75
|
+
[CLAUDE_CODE_2_1_233_PROFILE.id, BETA_REGISTRY_2_1_233],
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Selects the registry a profile composes against.
|
|
80
|
+
*
|
|
81
|
+
* An unrecognised id falls back to the 2.1.195 registry rather than throwing.
|
|
82
|
+
* Rejecting unknown profiles is the request builder's job -- it validates the
|
|
83
|
+
* profile before any of this runs -- and duplicating that rejection here would
|
|
84
|
+
* give `composeBetas` a second, differently-worded opinion about profile
|
|
85
|
+
* validity. Standalone callers keep the 2.1.195 behaviour they had before
|
|
86
|
+
* profiles were a parameter.
|
|
87
|
+
*/
|
|
88
|
+
function resolveBetaRegistry(
|
|
89
|
+
profile: ClaudeCodeProtocolProfile,
|
|
90
|
+
): ComposableBetaRegistry {
|
|
91
|
+
return PROFILE_BETA_REGISTRIES.get(profile.id) ?? BETA_REGISTRY;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface ComposeBetasInput {
|
|
95
|
+
readonly rawModel: string;
|
|
96
|
+
readonly normalizedId: string;
|
|
97
|
+
readonly capabilities: ClaudeCodeCapabilities;
|
|
98
|
+
readonly thinkingDisplayActive: boolean;
|
|
99
|
+
readonly cacheTtl?: "5m" | "1h" | null;
|
|
100
|
+
readonly speed?: "standard" | "fast" | null;
|
|
101
|
+
/**
|
|
102
|
+
* Package extension, not observed upstream behaviour. Consumer-supplied beta
|
|
103
|
+
* identifiers appended AFTER the derived canonical set. See
|
|
104
|
+
* `docs/source-trace.md`, governance ledger L10.
|
|
105
|
+
*/
|
|
106
|
+
readonly additionalBetas?: readonly string[];
|
|
107
|
+
/**
|
|
108
|
+
* Package extension, not observed upstream behaviour. Beta identifiers
|
|
109
|
+
* removed from the emitted set AFTER composition and AFTER the
|
|
110
|
+
* `additionalBetas` merge, so suppression beats addition. An identifier that
|
|
111
|
+
* is not in the composed set is a silent no-op. See `docs/source-trace.md`,
|
|
112
|
+
* governance ledger L14.
|
|
113
|
+
*/
|
|
114
|
+
readonly suppressBetas?: readonly string[];
|
|
115
|
+
/**
|
|
116
|
+
* Package extension, not observed upstream behaviour. Forces (`true`) or
|
|
117
|
+
* suppresses (`false`) the 1M-context beta for this request, overriding the
|
|
118
|
+
* `[1m]` model marker. See `docs/source-trace.md`, governance ledger L10.
|
|
119
|
+
*/
|
|
120
|
+
readonly use1MContextOverride?: boolean;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Reports the emitted beta set together with the identifiers `suppressBetas`
|
|
125
|
+
* actually removed. `suppressedBetaNames` is empty when the seam is unused or
|
|
126
|
+
* matched nothing, which is what keeps the evidence key absent for every
|
|
127
|
+
* request built before the seam existed.
|
|
128
|
+
*/
|
|
129
|
+
export interface ComposedBetas {
|
|
130
|
+
readonly betas: readonly string[];
|
|
131
|
+
readonly suppressedBetaNames: readonly string[];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Bounds a caller-supplied beta list (`additionalBetas` and `suppressBetas`
|
|
136
|
+
* share these rules verbatim). The header is a comma-joined single
|
|
137
|
+
* field, so a comma, control character, or whitespace in an entry would let a
|
|
138
|
+
* caller synthesize extra beta values (or, with CR/LF, an entirely separate
|
|
139
|
+
* header). The allowlist below is deliberately narrower than the observed
|
|
140
|
+
* upstream identifiers require, because every genuine beta name in
|
|
141
|
+
* `BETA_REGISTRY` matches it.
|
|
142
|
+
*/
|
|
143
|
+
const ADDITIONAL_BETA_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/u;
|
|
144
|
+
const MAX_ADDITIONAL_BETA_LENGTH = 128;
|
|
145
|
+
const MAX_ADDITIONAL_BETAS = 32;
|
|
146
|
+
|
|
147
|
+
function validateAdditionalBetas(value: unknown): readonly string[] {
|
|
148
|
+
if (!Array.isArray(value) || value.length > MAX_ADDITIONAL_BETAS) {
|
|
149
|
+
throw new ClaudeCodeWireError("INVALID_INPUT");
|
|
150
|
+
}
|
|
151
|
+
return value.map((entry: unknown): string => {
|
|
152
|
+
if (
|
|
153
|
+
typeof entry !== "string" ||
|
|
154
|
+
entry.length === 0 ||
|
|
155
|
+
entry.length > MAX_ADDITIONAL_BETA_LENGTH ||
|
|
156
|
+
!ADDITIONAL_BETA_PATTERN.test(entry)
|
|
157
|
+
) {
|
|
158
|
+
throw new ClaudeCodeWireError("INVALID_INPUT");
|
|
159
|
+
}
|
|
160
|
+
return entry;
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const NO_SUPPRESSED_BETAS: readonly string[] = Object.freeze([]);
|
|
165
|
+
|
|
166
|
+
export function composeBetas(
|
|
167
|
+
input: ComposeBetasInput,
|
|
168
|
+
profile: ClaudeCodeProtocolProfile = CLAUDE_CODE_2_1_195_PROFILE,
|
|
169
|
+
): readonly string[] {
|
|
170
|
+
return composeBetasWithAudit(input, profile).betas;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function composeBetasWithAudit(
|
|
174
|
+
input: ComposeBetasInput,
|
|
175
|
+
profile: ClaudeCodeProtocolProfile = CLAUDE_CODE_2_1_195_PROFILE,
|
|
176
|
+
): ComposedBetas {
|
|
177
|
+
const out: string[] = [];
|
|
178
|
+
const policy = profile.betaPolicy;
|
|
179
|
+
const experimental = policy.experimentalBetasEnabled;
|
|
180
|
+
const registry = resolveBetaRegistry(profile);
|
|
181
|
+
|
|
182
|
+
if (!input.normalizedId.includes("haiku"))
|
|
183
|
+
out.push(registry.CLAUDE_CODE.header);
|
|
184
|
+
if (policy.oauthAuthenticated) out.push(registry.OAUTH_AUTH.header);
|
|
185
|
+
// Package extension: `use1MContextOverride` replaces the model-marker gate
|
|
186
|
+
// for this request. The profile gate still applies, so an override cannot
|
|
187
|
+
// enable a beta the pinned profile declares unavailable.
|
|
188
|
+
const oneMillionRequested =
|
|
189
|
+
input.use1MContextOverride ?? /\[1m\]/iu.test(input.rawModel);
|
|
190
|
+
if (policy.oneMillionContextEnabled && oneMillionRequested) {
|
|
191
|
+
out.push(registry.LONG_CONTEXT.header);
|
|
192
|
+
}
|
|
193
|
+
if (
|
|
194
|
+
policy.interleavedThinkingEnabled &&
|
|
195
|
+
input.capabilities.interleavedThinking
|
|
196
|
+
) {
|
|
197
|
+
out.push(registry.INTERLEAVED_THINKING.header);
|
|
198
|
+
}
|
|
199
|
+
if (
|
|
200
|
+
experimental &&
|
|
201
|
+
input.capabilities.interleavedThinking &&
|
|
202
|
+
policy.interactive &&
|
|
203
|
+
!policy.thinkingSummariesShown &&
|
|
204
|
+
!input.thinkingDisplayActive
|
|
205
|
+
) {
|
|
206
|
+
out.push(registry.REDACT_THINKING.header);
|
|
207
|
+
}
|
|
208
|
+
if (
|
|
209
|
+
policy.thinkingTokenCountEnabled &&
|
|
210
|
+
experimental &&
|
|
211
|
+
input.capabilities.interleavedThinking
|
|
212
|
+
) {
|
|
213
|
+
out.push(registry.THINKING_TOKEN_COUNT.header);
|
|
214
|
+
}
|
|
215
|
+
/*
|
|
216
|
+
* The narration push site keeps its position in the sequence even when the
|
|
217
|
+
* resolved registry dropped the entry. Upstream removed the beta after
|
|
218
|
+
* 2.1.195, so a registry without it emits nothing here and the surrounding
|
|
219
|
+
* order closes up with no gap; the gates are still evaluated first so a
|
|
220
|
+
* profile that enables narration against a registry that has it behaves
|
|
221
|
+
* exactly as it did before.
|
|
222
|
+
*/
|
|
223
|
+
const narrationSummaries = registry.NARRATION_SUMMARIES;
|
|
224
|
+
if (
|
|
225
|
+
experimental &&
|
|
226
|
+
policy.narrationSummariesEnabled &&
|
|
227
|
+
narrationSummaries !== undefined
|
|
228
|
+
) {
|
|
229
|
+
out.push(narrationSummaries.header);
|
|
230
|
+
}
|
|
231
|
+
if (experimental && input.capabilities.contextManagement)
|
|
232
|
+
out.push(registry.CONTEXT_MANAGEMENT.header);
|
|
233
|
+
if (
|
|
234
|
+
experimental &&
|
|
235
|
+
supportsStructuredOutputs(input.normalizedId) &&
|
|
236
|
+
policy.structuredOutputsEnabled
|
|
237
|
+
) {
|
|
238
|
+
out.push(registry.STRUCTURED_OUTPUTS.header);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// No web-search beta: upstream pushes it only for vertex and foundry.
|
|
242
|
+
if (experimental) out.push(registry.PROMPT_CACHING_SCOPE.header);
|
|
243
|
+
if (supportsMidConversationSystem(input.normalizedId, profile))
|
|
244
|
+
out.push(registry.MID_CONVERSATION_SYSTEM.header);
|
|
245
|
+
if (input.capabilities.effort) out.push(registry.EFFORT.header);
|
|
246
|
+
|
|
247
|
+
if (input.speed === "fast" && !out.includes(registry.SPEED.header)) {
|
|
248
|
+
out.push(registry.SPEED.header);
|
|
249
|
+
}
|
|
250
|
+
if (policy.afkModeEnabled && !out.includes(registry.AFK_MODE.header)) {
|
|
251
|
+
out.push(registry.AFK_MODE.header);
|
|
252
|
+
}
|
|
253
|
+
if (
|
|
254
|
+
input.cacheTtl === "1h" &&
|
|
255
|
+
experimental &&
|
|
256
|
+
!out.includes(registry.EXTENDED_CACHE_TTL.header)
|
|
257
|
+
) {
|
|
258
|
+
out.push(registry.EXTENDED_CACHE_TTL.header);
|
|
259
|
+
}
|
|
260
|
+
if (profile.contextHintEnabled) out.push(registry.CONTEXT_HINT.header);
|
|
261
|
+
if (
|
|
262
|
+
policy.cacheDiagnosisEnabled &&
|
|
263
|
+
!out.includes(registry.CACHE_DIAGNOSIS.header)
|
|
264
|
+
) {
|
|
265
|
+
out.push(registry.CACHE_DIAGNOSIS.header);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// No advisor-tool beta: upstream has no observed unconditional push site.
|
|
269
|
+
|
|
270
|
+
// Package extension. Canonical, upstream-derived identifiers always precede
|
|
271
|
+
// caller-supplied ones, and a caller entry that duplicates an already-emitted
|
|
272
|
+
// identifier is dropped rather than reordering the canonical prefix.
|
|
273
|
+
if (input.additionalBetas !== undefined) {
|
|
274
|
+
for (const beta of validateAdditionalBetas(input.additionalBetas)) {
|
|
275
|
+
if (!out.includes(beta)) out.push(beta);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Package extension. The suppression filter is deliberately LAST: it runs
|
|
280
|
+
// after the canonical composition and after the `additionalBetas` merge, so
|
|
281
|
+
// an identifier named by both seams does not reach the wire. Removal is
|
|
282
|
+
// reported in composed order, never in caller order, and an identifier that
|
|
283
|
+
// was never composed is a silent no-op because a consumer cannot know which
|
|
284
|
+
// betas this package derives for a given model.
|
|
285
|
+
if (input.suppressBetas === undefined) {
|
|
286
|
+
return Object.freeze({
|
|
287
|
+
betas: Object.freeze(out),
|
|
288
|
+
suppressedBetaNames: NO_SUPPRESSED_BETAS,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
const suppressed = new Set(validateAdditionalBetas(input.suppressBetas));
|
|
292
|
+
const kept: string[] = [];
|
|
293
|
+
const removed: string[] = [];
|
|
294
|
+
for (const beta of out) {
|
|
295
|
+
if (suppressed.has(beta)) removed.push(beta);
|
|
296
|
+
else kept.push(beta);
|
|
297
|
+
}
|
|
298
|
+
return Object.freeze({
|
|
299
|
+
betas: Object.freeze(kept),
|
|
300
|
+
suppressedBetaNames: Object.freeze(removed),
|
|
301
|
+
});
|
|
302
|
+
}
|