@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.
Files changed (80) hide show
  1. package/CHANGELOG.md +101 -2
  2. package/README.md +67 -2
  3. package/dist/betas.d.ts +37 -0
  4. package/dist/betas.d.ts.map +1 -1
  5. package/dist/betas.js +55 -23
  6. package/dist/betas.js.map +1 -1
  7. package/dist/build-request.d.ts +11 -0
  8. package/dist/build-request.d.ts.map +1 -1
  9. package/dist/build-request.js +133 -11
  10. package/dist/build-request.js.map +1 -1
  11. package/dist/contracts.d.ts +53 -0
  12. package/dist/contracts.d.ts.map +1 -1
  13. package/dist/contracts.js.map +1 -1
  14. package/dist/fingerprint.d.ts +29 -2
  15. package/dist/fingerprint.d.ts.map +1 -1
  16. package/dist/fingerprint.js +60 -7
  17. package/dist/fingerprint.js.map +1 -1
  18. package/dist/headers.d.ts.map +1 -1
  19. package/dist/headers.js +12 -4
  20. package/dist/headers.js.map +1 -1
  21. package/dist/index.d.ts +1 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +1 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/model-capabilities.d.ts +31 -3
  26. package/dist/model-capabilities.d.ts.map +1 -1
  27. package/dist/model-capabilities.js +145 -12
  28. package/dist/model-capabilities.js.map +1 -1
  29. package/dist/models.d.ts.map +1 -1
  30. package/dist/models.js +8 -4
  31. package/dist/models.js.map +1 -1
  32. package/dist/profile-behaviors.d.ts +61 -0
  33. package/dist/profile-behaviors.d.ts.map +1 -0
  34. package/dist/profile-behaviors.js +53 -0
  35. package/dist/profile-behaviors.js.map +1 -0
  36. package/dist/profiles/beta-registry-2.1.233.d.ts +140 -0
  37. package/dist/profiles/beta-registry-2.1.233.d.ts.map +1 -0
  38. package/dist/profiles/beta-registry-2.1.233.js +183 -0
  39. package/dist/profiles/beta-registry-2.1.233.js.map +1 -0
  40. package/dist/profiles/claude-code-2.1.195.d.ts.map +1 -1
  41. package/dist/profiles/claude-code-2.1.195.js +14 -0
  42. package/dist/profiles/claude-code-2.1.195.js.map +1 -1
  43. package/dist/profiles/claude-code-2.1.233.d.ts +3 -0
  44. package/dist/profiles/claude-code-2.1.233.d.ts.map +1 -0
  45. package/dist/profiles/claude-code-2.1.233.js +235 -0
  46. package/dist/profiles/claude-code-2.1.233.js.map +1 -0
  47. package/dist/redaction.d.ts.map +1 -1
  48. package/dist/redaction.js +14 -1
  49. package/dist/redaction.js.map +1 -1
  50. package/dist/request-body.d.ts.map +1 -1
  51. package/dist/request-body.js +12 -10
  52. package/dist/request-body.js.map +1 -1
  53. package/dist/thinking.d.ts +33 -7
  54. package/dist/thinking.d.ts.map +1 -1
  55. package/dist/thinking.js +105 -36
  56. package/dist/thinking.js.map +1 -1
  57. package/package.json +10 -2
  58. package/src/anti-verbosity.ts +219 -0
  59. package/src/beta-registry.ts +140 -0
  60. package/src/betas.ts +302 -0
  61. package/src/build-request.ts +1799 -0
  62. package/src/contracts.ts +1286 -0
  63. package/src/count-tokens.ts +84 -0
  64. package/src/fingerprint.ts +155 -0
  65. package/src/headers.ts +448 -0
  66. package/src/index.ts +63 -0
  67. package/src/metadata.ts +331 -0
  68. package/src/model-capabilities.ts +453 -0
  69. package/src/model-identity.ts +45 -0
  70. package/src/models.ts +50 -0
  71. package/src/profile-behaviors.ts +114 -0
  72. package/src/profiles/beta-registry-2.1.233.ts +200 -0
  73. package/src/profiles/claude-code-2.1.195.ts +168 -0
  74. package/src/profiles/claude-code-2.1.233.ts +240 -0
  75. package/src/redaction.ts +536 -0
  76. package/src/request-body.ts +1931 -0
  77. package/src/sha256.ts +114 -0
  78. package/src/system-prompt.ts +222 -0
  79. package/src/thinking.ts +346 -0
  80. package/src/unicode.ts +24 -0
package/dist/thinking.js CHANGED
@@ -1,9 +1,23 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-or-later
2
+ import { profileBehaviors } from "./profile-behaviors.js";
3
+ import { CLAUDE_CODE_2_1_195_PROFILE } from "./profiles/claude-code-2.1.195.js";
2
4
  /**
3
5
  * Per-model output token limits, ported from upstream `Xxe` at byte offset
4
- * 227378240. Keyed on the NORMALISED model id, and deliberately independent of
5
- * the catalogue: `claude-3-opus`, `claude-3-sonnet` and `claude-3-haiku` are
6
- * reachable through the normaliser but have no catalogue entry.
6
+ * 227378240. Keyed on the NORMALISED model id.
7
+ *
8
+ * Resolution order, since Fase 1.2:
9
+ *
10
+ * 1. The pinned 2.1.195 catalogue, when the id has an entry carrying
11
+ * `maxOutputTokens`. That is the single source of truth for every model
12
+ * the profile knows, and `token-limits-equivalence.test.ts` pins the two
13
+ * sources cell by cell.
14
+ * 2. Otherwise the transcribed `Xxe` table below, preserved intact as the
15
+ * demarcated fallback. It is NOT dead code and must not be trimmed to
16
+ * "only the ids the catalogue lacks": `claude-3-opus`, `claude-3-sonnet`
17
+ * and `claude-3-haiku` are reachable through the normaliser with no
18
+ * catalogue entry, `claude-mythos-5` is absent from the catalogue by
19
+ * product decision D-1, and any id from a newer client lands on the
20
+ * final fallback row.
7
21
  *
8
22
  * Both fields are load-bearing. `upperLimit` seeds the thinking budget when the
9
23
  * caller supplies none (upstream `wvi = Xxe(e).upperLimit - 1`); `default` caps
@@ -17,33 +31,84 @@
17
31
  * override this package cannot observe.
18
32
  * - `bvi(e)` adjusts BOTH fields, but sits behind `_vi()`, which returns a
19
33
  * hard `false`. Dead code upstream.
34
+ *
35
+ * From 2.1.222 onward upstream grew a THIRD adjustment, this one derived from
36
+ * the request rather than from host state, and therefore observable: see
37
+ * `requestedMaxTokens` below.
38
+ *
39
+ * @param requestedMaxTokens
40
+ * The caller's own `max_tokens`, when the call site has it. Modelled for
41
+ * profiles from 2.1.222 onward only; see the demarcated block below.
20
42
  */
21
- export function modelOutputTokenLimits(normalizedId) {
22
- if (normalizedId === "claude-fable-5" || normalizedId === "claude-mythos-5") {
23
- return { default: 64000, upperLimit: 128000 };
24
- }
25
- if (normalizedId === "claude-opus-4-8") {
26
- return { default: 64000, upperLimit: 128000 };
27
- }
28
- if (normalizedId === "claude-opus-4-7") {
29
- return { default: 64000, upperLimit: 128000 };
43
+ export function modelOutputTokenLimits(normalizedId, profile = CLAUDE_CODE_2_1_195_PROFILE, requestedMaxTokens) {
44
+ const resolved = resolveDeclaredLimits(normalizedId, profile);
45
+ /*
46
+ * ---- Demarcated: request-derived upper bound, upstream 2.1.222+. ----
47
+ *
48
+ * Upstream raises `upperLimit` to the caller's own `max_tokens` and lowers
49
+ * `default` to fit under it:
50
+ *
51
+ * upperLimit = requestedMaxTokens;
52
+ * default = Math.min(default, upperLimit);
53
+ *
54
+ * Verified byte-identical between upstream 2.1.222 and 2.1.233.
55
+ *
56
+ * The gate is STRUCTURAL, not a capability flag: this behaviour exists in
57
+ * upstream 2.1.222+ and 2.1.195 does not have it, so the 195 profile must
58
+ * never see it. Which profiles are on which side is `profile-behaviors.ts`'s
59
+ * question, not this module's.
60
+ *
61
+ * `Number.isSafeInteger` is deliberately stricter than upstream's truthy
62
+ * check. The upstream runtime only ever produces integers in this field, so
63
+ * the two agree on every reachable input; here a NaN or Infinity would
64
+ * propagate straight into `budget_tokens`, which must stay an integer.
65
+ */
66
+ if (profileBehaviors(profile).requestDerivedTokenCeiling &&
67
+ requestedMaxTokens !== undefined &&
68
+ Number.isSafeInteger(requestedMaxTokens) &&
69
+ requestedMaxTokens >= 4096) {
70
+ const upperLimit = requestedMaxTokens;
71
+ return { default: Math.min(resolved.default, upperLimit), upperLimit };
30
72
  }
31
- if (normalizedId === "claude-sonnet-4-6") {
32
- return { default: 32000, upperLimit: 128000 };
73
+ return resolved;
74
+ }
75
+ function resolveDeclaredLimits(normalizedId, profile) {
76
+ const declared = profile.supportedModels[normalizedId]?.maxOutputTokens;
77
+ if (declared !== undefined) {
78
+ // `upper` is the catalogue's name for what this module calls `upperLimit`;
79
+ // the rename happens here and nowhere else.
80
+ return { default: declared.default, upperLimit: declared.upper };
33
81
  }
34
- if (normalizedId === "claude-opus-4-6") {
82
+ /*
83
+ * ---- Demarcated fallback: the reachable remainder of `Xxe`. ----
84
+ *
85
+ * No catalogue id reaches this point. Every entry of the 2.1.195 catalogue
86
+ * declares `maxOutputTokens`, and `capability-equivalence.test.ts` fails if
87
+ * one stops doing so, which is what keeps the rows below to the ids the
88
+ * catalogue genuinely cannot answer for:
89
+ *
90
+ * - `claude-mythos-5` has no catalogue entry by product decision D-1.
91
+ * - `claude-3-opus`, `claude-3-sonnet` and `claude-3-haiku` are reachable
92
+ * through the normaliser and predate the catalogue.
93
+ *
94
+ * The rows for catalogued ids were deleted rather than kept "just in case":
95
+ * they were unreachable, so they could be neither covered nor
96
+ * mutation-killed, and a second copy of a limit that no longer serves any
97
+ * request is exactly the duplicated table
98
+ * `test/governance/single-source-of-truth.test.ts` exists to prevent.
99
+ *
100
+ * This mirrors upstream 2.1.222, where derivation is catalogue-first and
101
+ * the surviving legacy rows are the `claude-3-*` ones plus a generic tail.
102
+ *
103
+ * If a future profile omits `maxOutputTokens` for some id, that id lands on
104
+ * the generic tail below -- 32000/128000 -- rather than on a stale
105
+ * per-model row. That is deliberate: a wrong-but-loud generic limit is
106
+ * recoverable, a silently stale per-model limit is not. The equivalence
107
+ * guard fires first in any case.
108
+ */
109
+ if (normalizedId === "claude-mythos-5") {
35
110
  return { default: 64000, upperLimit: 128000 };
36
111
  }
37
- if (normalizedId === "claude-opus-4-5" ||
38
- normalizedId === "claude-sonnet-4-0" ||
39
- normalizedId === "claude-sonnet-4-5" ||
40
- normalizedId === "claude-haiku-4-5") {
41
- return { default: 32000, upperLimit: 64000 };
42
- }
43
- if (normalizedId === "claude-opus-4-1" ||
44
- normalizedId === "claude-opus-4-0") {
45
- return { default: 32000, upperLimit: 32000 };
46
- }
47
112
  if (normalizedId === "claude-3-opus") {
48
113
  return { default: 4096, upperLimit: 4096 };
49
114
  }
@@ -53,13 +118,6 @@ export function modelOutputTokenLimits(normalizedId) {
53
118
  if (normalizedId === "claude-3-haiku") {
54
119
  return { default: 4096, upperLimit: 4096 };
55
120
  }
56
- if (normalizedId === "claude-3-5-sonnet" ||
57
- normalizedId === "claude-3-5-haiku") {
58
- return { default: 8192, upperLimit: 8192 };
59
- }
60
- if (normalizedId === "claude-3-7-sonnet") {
61
- return { default: 32000, upperLimit: 64000 };
62
- }
63
121
  return { default: 32000, upperLimit: 128000 };
64
122
  }
65
123
  /**
@@ -81,9 +139,15 @@ export function modelOutputTokenLimits(normalizedId) {
81
139
  * Upstream uses `||`, not `??`, so a zero override would fall back to the
82
140
  * default. Unreachable here: `max_tokens` is validated as a positive integer
83
141
  * before this runs.
142
+ *
143
+ * `requested` is forwarded as the request-derived bound so that this call site
144
+ * reads the same table upstream reads. It cannot change the result: the
145
+ * override only ever lowers `default` to `requested`, and
146
+ * `min(requested, min(default, requested)) === min(requested, default)`. It is
147
+ * passed for coherence of reading, not for effect.
84
148
  */
85
- export function clampMaxTokens(requested, normalizedId) {
86
- return Math.min(requested, modelOutputTokenLimits(normalizedId).default);
149
+ export function clampMaxTokens(requested, normalizedId, profile = CLAUDE_CODE_2_1_195_PROFILE) {
150
+ return Math.min(requested, modelOutputTokenLimits(normalizedId, profile, requested).default);
87
151
  }
88
152
  /**
89
153
  * Upstream `Yn = nr && CM() && QOt(u) ? n.display : void 0`, combined with the
@@ -121,7 +185,7 @@ export function isThinkingDisplayActive(request, capabilities, betaPolicy) {
121
185
  * adaptive. Serialised bodies are compared byte for byte, so the insertion
122
186
  * order below must not be rearranged.
123
187
  */
124
- export function resolveThinking(request, normalizedId, capabilities, betaPolicy, maxTokens) {
188
+ export function resolveThinking(request, normalizedId, capabilities, betaPolicy, maxTokens, profile = CLAUDE_CODE_2_1_195_PROFILE) {
125
189
  // Upstream `nr = n.type !== "disabled" && !CLAUDE_CODE_DISABLE_THINKING`.
126
190
  const requestActive = request !== undefined && request.type !== "disabled";
127
191
  const displayActive = isThinkingDisplayActive(request, capabilities, betaPolicy);
@@ -137,8 +201,13 @@ export function resolveThinking(request, normalizedId, capabilities, betaPolicy,
137
201
  // Upstream: `let Tr = wvi(u)` — the model's upper limit minus one —
138
202
  // overridden by the caller's budget when supplied, then clamped by
139
203
  // `Tr = Math.min(Fi - 1, Tr)` where `Fi` is the emitted `max_tokens`.
204
+ //
205
+ // This is the one wire-visible consumer of the request-derived bound: on
206
+ // a 2.1.222+ profile a caller asking for a `max_tokens` above the
207
+ // catalogue's upper limit seeds the default budget from THEIR number
208
+ // minus one, not from the catalogue's.
140
209
  const requested = request.budgetTokens ??
141
- modelOutputTokenLimits(normalizedId).upperLimit - 1;
210
+ modelOutputTokenLimits(normalizedId, profile, maxTokens).upperLimit - 1;
142
211
  emitted = { budget_tokens: Math.min(maxTokens - 1, requested) };
143
212
  emitted["type"] = "enabled";
144
213
  if (display !== undefined)
@@ -1 +1 @@
1
- {"version":3,"file":"thinking.js","sourceRoot":"","sources":["../src/thinking.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"thinking.js","sourceRoot":"","sources":["../src/thinking.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tormentalabs/claude-code-wire-compat",
3
- "version": "0.1.0-rc.17",
3
+ "version": "0.2.0",
4
4
  "description": "Runtime-neutral Claude Code Messages wire compatibility primitives",
5
5
  "type": "module",
6
6
  "private": false,
@@ -19,6 +19,7 @@
19
19
  },
20
20
  "files": [
21
21
  "dist",
22
+ "src",
22
23
  "README.md",
23
24
  "LICENSE",
24
25
  "NOTICE",
@@ -32,6 +33,10 @@
32
33
  "./profiles/claude-code-2.1.195": {
33
34
  "types": "./dist/profiles/claude-code-2.1.195.d.ts",
34
35
  "import": "./dist/profiles/claude-code-2.1.195.js"
36
+ },
37
+ "./profiles/claude-code-2.1.233": {
38
+ "types": "./dist/profiles/claude-code-2.1.233.d.ts",
39
+ "import": "./dist/profiles/claude-code-2.1.233.js"
35
40
  }
36
41
  },
37
42
  "types": "./dist/index.d.ts",
@@ -48,7 +53,10 @@
48
53
  "test:coverage": "vitest run --coverage",
49
54
  "test:pack": "node scripts/verify-packed-consumers.mjs",
50
55
  "pack:check": "npm pack --dry-run && publint && attw --pack . --profile esm-only",
51
- "drift:check": "node scripts/verify-drift.mjs"
56
+ "drift:check": "node scripts/verify-drift.mjs",
57
+ "fixtures:check": "node scripts/seal-golden-fixtures.mjs --check",
58
+ "fixtures:seal": "node scripts/seal-golden-fixtures.mjs --write",
59
+ "extract:profile": "node scripts/extract-upstream-profile.mjs"
52
60
  },
53
61
  "devDependencies": {
54
62
  "@arethetypeswrong/cli": "0.18.5",
@@ -0,0 +1,219 @@
1
+ // SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+ import type {
4
+ AntiVerbosityPolicy,
5
+ AntiVerbositySection,
6
+ ClaudeCodeProtocolProfile,
7
+ } from "./contracts.js";
8
+ import { ClaudeCodeWireError } from "./contracts.js";
9
+ import { normalizeModelId } from "./model-identity.js";
10
+ import { CLAUDE_CODE_2_1_195_PROFILE } from "./profiles/claude-code-2.1.195.js";
11
+
12
+ /**
13
+ * The genuine client's anti-verbosity system-prompt section, and the three-way
14
+ * selector that chooses between its variants.
15
+ *
16
+ * Upstream this is `ytm(e)` at byte offset 238083640 of the
17
+ * pinned client build. The four strings below were not transcribed by hand: they
18
+ * were produced by executing the client's own template literals against stubbed
19
+ * predicates. The forensic dump tooling collapses non-printable runs and so
20
+ * cannot be trusted for whitespace inside a template literal; the branch-3
21
+ * heading in particular is followed by a newline, not a space, which a
22
+ * dump-based reading got wrong.
23
+ *
24
+ * The package EXPOSES this text and never injects it. `buildClaudeCodeRequest`
25
+ * does not consult this module. The genuine client assembles a large system
26
+ * prompt from many sections and this package models only the protocol envelope,
27
+ * so silently adding one section would produce a body matching neither the
28
+ * client nor the caller's intent. This is the same boundary already settled for
29
+ * `defaultEffort` and for cache-breakpoint placement: expose, let the caller
30
+ * decide.
31
+ */
32
+
33
+ /** Mirrors upstream defaults: a stock first-party install reports both false. */
34
+ export const DEFAULT_ANTI_VERBOSITY_POLICY: AntiVerbosityPolicy = Object.freeze(
35
+ {
36
+ briefModeEnabled: false,
37
+ pewterOwlToolEnabled: false,
38
+ },
39
+ );
40
+
41
+ /**
42
+ * Branch 1 with upstream `htm` true, which is the default because `htm` is
43
+ * `!(isBriefEnabled() || pewterOwlTool())`. Carries the extra paragraph about
44
+ * text written between tool calls.
45
+ */
46
+ export const COMMUNICATING_WITH_THE_USER_FULL =
47
+ "# Communicating with the user\n" +
48
+ "\n" +
49
+ "Your text output is what the user reads; they usually can't see your thinking or the raw tool results. Write it for a teammate who stepped away and is catching up, not for a log file: they don't know the codenames or shorthand you created along the way, and they didn't watch your process unfold. Before your first tool call, say in a sentence what you're about to do; while working, give brief updates when you find something load-bearing or change direction.\n" +
50
+ "\n" +
51
+ "Text you write between tool calls may not be shown to the user. Everything the user needs from this turn — answers, summaries, findings, conclusions, deliverables — must be in the final text message of your turn, with no tool calls after it. Keep text between tool calls to brief status notes. If something important appeared only mid-turn or in your thinking, restate it in that final message.\n" +
52
+ "\n" +
53
+ 'Lead with the outcome. Your first sentence after finishing should answer "what happened" or "what did you find" — the thing the user would ask for if they said "just give me the TLDR." Supporting detail and reasoning come after, for readers who want them.\n' +
54
+ "\n" +
55
+ "Being readable and being concise are different things, and readable matters more. If the user has to reread your summary or ask you to explain, any time saved by brevity is gone. The way to keep output short is to be selective about what you include (drop details that don't change what the reader would do next), not to compress the writing into fragments, abbreviations, arrow chains like `A → B → fails`, or jargon. What you do include, write in complete sentences with the technical terms spelled out. Don't make the reader cross-reference labels or numbering you invented earlier; say what you mean in place.\n" +
56
+ "\n" +
57
+ "Match the response to the question: a simple question gets a direct answer in prose, not headers and sections. Use tables only for short enumerable facts, with explanations in the surrounding prose rather than the cells. Calibrate to the user — a bit tighter for an expert, more explanatory for someone newer.\n" +
58
+ "\n" +
59
+ "Write code that reads like the surrounding code: match its comment density, naming, and idiom.\n" +
60
+ "Only write a code comment to state a constraint the code itself can't show — never to say where it came from, what the next line does, or why your change is correct; that's you talking to the reviewer, not the next reader, and it's noise the moment the PR merges.";
61
+
62
+ /** Branch 1 with upstream `htm` false: brief mode or the pewter-owl tool is on. */
63
+ export const COMMUNICATING_WITH_THE_USER_CONDENSED =
64
+ "# Communicating with the user\n" +
65
+ "\n" +
66
+ "Your text output is what the user reads between tool calls; they usually can't see your thinking or the raw tool results. Write it for a teammate who stepped away and is catching up, not for a log file: they don't know the codenames or shorthand you created along the way, and they didn't watch your process unfold. Before your first tool call, say in a sentence what you're about to do; while working, give brief updates when you find something load-bearing or change direction.\n" +
67
+ "\n" +
68
+ 'Lead with the outcome. Your first sentence after finishing should answer "what happened" or "what did you find" — the thing the user would ask for if they said "just give me the TLDR." Supporting detail and reasoning come after, for readers who want them.\n' +
69
+ "\n" +
70
+ "Being readable and being concise are different things, and readable matters more. If the user has to reread your summary or ask you to explain, any time saved by brevity is gone. The way to keep output short is to be selective about what you include (drop details that don't change what the reader would do next), not to compress the writing into fragments, abbreviations, arrow chains like `A → B → fails`, or jargon. What you do include, write in complete sentences with the technical terms spelled out. Don't make the reader cross-reference labels or numbering you invented earlier; say what you mean in place.\n" +
71
+ "\n" +
72
+ "Match the response to the question: a simple question gets a direct answer in prose, not headers and sections. Use tables only for short enumerable facts, with explanations in the surrounding prose rather than the cells. Calibrate to the user — a bit tighter for an expert, more explanatory for someone newer.\n" +
73
+ "\n" +
74
+ "Write code that reads like the surrounding code: match its comment density, naming, and idiom.\n" +
75
+ "Only write a code comment to state a constraint the code itself can't show — never to say where it came from, what the next line does, or why your change is correct; that's you talking to the reviewer, not the next reader, and it's noise the moment the PR merges.";
76
+
77
+ /** Branch 2, upstream `ph(e)` true. A single sentence. */
78
+ export const LEAN_SECTION =
79
+ "Write code that reads like the surrounding code: match its comment density, naming, and idiom.";
80
+
81
+ /** Branch 3, the fallthrough. */
82
+ export const TEXT_OUTPUT_SECTION =
83
+ "# Text output (does not apply to tool calls)\n" +
84
+ "Assume users can't see most tool calls or thinking — only your text output. Before your first tool call, state in one sentence what you're about to do. While working, give short updates at key moments: when you find something, when you change direction, or when you hit a blocker. Brief is good — silent is not. One sentence per update is almost always enough.\n" +
85
+ "\n" +
86
+ "Don't narrate your internal deliberation. User-facing text should be relevant communication to the user, not a running commentary on your thought process. State results and decisions directly, and focus user-facing text on relevant updates for the user.\n" +
87
+ "\n" +
88
+ "When you do write updates, write so the reader can pick up cold: complete sentences, no unexplained jargon or shorthand from earlier in the session. But keep it tight — a clear sentence is better than a clear paragraph.\n" +
89
+ "\n" +
90
+ "End-of-turn summary: one or two sentences. What changed and what's next. Nothing else.\n" +
91
+ "\n" +
92
+ "Match responses to the task: a simple question gets a direct answer, not headers and sections.\n" +
93
+ "\n" +
94
+ "In code: default to writing no comments. Never write multi-paragraph docstrings or multi-line comment blocks — one short line max. Don't create planning, decision, or analysis documents unless the user asks for them — work from conversation context, not intermediate files.";
95
+
96
+ /**
97
+ * SHA-256 of each constant as executed from the genuine client. A test pins
98
+ * these so any edit to the text above fails loudly rather than silently
99
+ * shipping a divergent prompt.
100
+ */
101
+ export const ANTI_VERBOSITY_DIGESTS = Object.freeze({
102
+ communicatingWithTheUserFull:
103
+ "41a8a87303e6f6f8224906daf9741fd6be495b79400854d92078991d15e9c56c",
104
+ communicatingWithTheUserCondensed:
105
+ "7028dc6d1492b7616b9b5f2f58416c09a0db5cd671f1032fbaeb9120ad51437b",
106
+ lean: "ee43af37398581e92bde06d341c98c7b7a9ff6c56023c2bc17b9feaf2d6e31ea",
107
+ textOutput:
108
+ "c184a5d4b4b6a0fc374a37c69f72937abf38bcccaa2d0cce0427968fcda3ccc7",
109
+ });
110
+
111
+ /** Upstream `i_e`, deliberately applied to the RAW caller string. */
112
+ const EAP_PATTERN = /-eap($|\[)/iu;
113
+
114
+ function catalogueCapability(
115
+ normalizedId: string,
116
+ capability: string,
117
+ profile: ClaudeCodeProtocolProfile,
118
+ ): boolean {
119
+ if (!Object.hasOwn(profile.supportedModels, normalizedId)) return false;
120
+ return (
121
+ profile.supportedModels[normalizedId]?.capabilities.includes(capability) ===
122
+ true
123
+ );
124
+ }
125
+
126
+ /**
127
+ * Upstream `Mte`. This is one of the few places where the catalogue capability
128
+ * array is genuinely load-bearing: unlike the nine model-capability predicates,
129
+ * `Mte` has no provider fallback, so the membership test decides the result.
130
+ */
131
+ function hasFableMitigations(
132
+ normalizedId: string,
133
+ profile: ClaudeCodeProtocolProfile,
134
+ ): boolean {
135
+ return (
136
+ catalogueCapability(normalizedId, "fable_5_mitigations", profile) ||
137
+ normalizedId === "claude-mythos-5"
138
+ );
139
+ }
140
+
141
+ /**
142
+ * Upstream `Kkd`. Its trailing `return !td()` is false on the first-party
143
+ * provider this profile pins, so an unrecognised identifier falls through to
144
+ * the lean branch rather than the text-output branch.
145
+ *
146
+ * Upstream also carries `|| t === "claude-mythos-5"` beside the
147
+ * `lean_prompt` test. It is omitted here because it is unreachable: the only
148
+ * caller tests `hasFableMitigations` first, which already claims mythos-5 for
149
+ * the communicating-with-the-user branch. Upstream shares `Kkd` with other
150
+ * call sites and so still needs it. Restore it if this helper ever gains a
151
+ * second caller.
152
+ */
153
+ function usesTextOutputSection(
154
+ rawModel: string,
155
+ profile: ClaudeCodeProtocolProfile,
156
+ ): boolean {
157
+ if (EAP_PATTERN.test(rawModel)) return false;
158
+ const id = normalizeModelId(rawModel);
159
+ if (catalogueCapability(id, "lean_prompt", profile)) return false;
160
+ return (
161
+ id.includes("claude-3-") ||
162
+ id.includes("haiku") ||
163
+ id.includes("sonnet") ||
164
+ id === "claude-opus-4-0" ||
165
+ id === "claude-opus-4-1" ||
166
+ id === "claude-opus-4-5" ||
167
+ id === "claude-opus-4-6" ||
168
+ id === "claude-opus-4-7"
169
+ );
170
+ }
171
+
172
+ /**
173
+ * Reports which branch of upstream `ytm` a model selects.
174
+ *
175
+ * Upstream `gtm` is `return !1`, so branch 1 is gated on `Mte` alone. Note
176
+ * that `ytm` passes the NORMALISED id to `Mte` but the RAW caller string to
177
+ * `ph`, because `ph` reaches `i_e`, which must see an unnormalised
178
+ * `-eap` suffix. That asymmetry is reproduced here.
179
+ */
180
+ export function selectAntiVerbositySection(
181
+ rawModel: string,
182
+ profile: ClaudeCodeProtocolProfile = CLAUDE_CODE_2_1_195_PROFILE,
183
+ ): AntiVerbositySection {
184
+ if (typeof rawModel !== "string" || rawModel.length === 0) {
185
+ throw new ClaudeCodeWireError("INVALID_INPUT");
186
+ }
187
+ if (hasFableMitigations(normalizeModelId(rawModel), profile)) {
188
+ return "communicating-with-the-user";
189
+ }
190
+ return usesTextOutputSection(rawModel, profile) ? "text-output" : "lean";
191
+ }
192
+
193
+ /** Returns the exact section text the genuine client would emit for a model. */
194
+ export function antiVerbosityText(
195
+ rawModel: string,
196
+ policy: AntiVerbosityPolicy = DEFAULT_ANTI_VERBOSITY_POLICY,
197
+ profile: ClaudeCodeProtocolProfile = CLAUDE_CODE_2_1_195_PROFILE,
198
+ ): string {
199
+ // Validated eagerly, and for every section rather than only the one that
200
+ // reads it, so a malformed policy fails the same way regardless of which
201
+ // model it is paired with. `selectAntiVerbositySection` validates its own
202
+ // argument the same way despite the declared types, because callers reach
203
+ // this module across an untyped boundary.
204
+ const candidate: unknown = policy;
205
+ if (candidate === null || typeof candidate !== "object") {
206
+ throw new ClaudeCodeWireError("INVALID_INPUT");
207
+ }
208
+ const brief: unknown = Reflect.get(candidate, "briefModeEnabled");
209
+ const pewterOwl: unknown = Reflect.get(candidate, "pewterOwlToolEnabled");
210
+ if (typeof brief !== "boolean" || typeof pewterOwl !== "boolean") {
211
+ throw new ClaudeCodeWireError("INVALID_INPUT");
212
+ }
213
+ const section = selectAntiVerbositySection(rawModel, profile);
214
+ if (section === "lean") return LEAN_SECTION;
215
+ if (section === "text-output") return TEXT_OUTPUT_SECTION;
216
+ return brief || pewterOwl
217
+ ? COMMUNICATING_WITH_THE_USER_CONDENSED
218
+ : COMMUNICATING_WITH_THE_USER_FULL;
219
+ }
@@ -0,0 +1,140 @@
1
+ // SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+ interface BetaRegistryEntry {
4
+ readonly featureKey: string;
5
+ readonly header: string;
6
+ }
7
+
8
+ function deepFreeze<T>(value: T): T {
9
+ if (value !== null && typeof value === "object") {
10
+ for (const key of Reflect.ownKeys(value)) {
11
+ deepFreeze(Reflect.get(value, key));
12
+ }
13
+ Object.freeze(value);
14
+ }
15
+ return value;
16
+ }
17
+
18
+ /** Genuine-client beta registry, upstream `Udd`. */
19
+ export const BETA_REGISTRY = deepFreeze({
20
+ CLAUDE_CODE: { featureKey: "claude_code", header: "claude-code-20250219" }, // Y2e
21
+ OAUTH_AUTH: { featureKey: "oauth_auth", header: "oauth-2025-04-20" }, // qIe
22
+ INTERLEAVED_THINKING: {
23
+ featureKey: "interleaved_thinking",
24
+ header: "interleaved-thinking-2025-05-14",
25
+ }, // Gnt
26
+ LONG_CONTEXT: { featureKey: "long_context", header: "context-1m-2025-08-07" }, // FY
27
+ CONTEXT_MANAGEMENT: {
28
+ featureKey: "context_management",
29
+ header: "context-management-2025-06-27",
30
+ }, // X2e
31
+ STRUCTURED_OUTPUTS: {
32
+ featureKey: "structured_outputs",
33
+ header: "structured-outputs-2025-12-15",
34
+ }, // lte
35
+ WEB_SEARCH: { featureKey: "web_search", header: "web-search-2025-03-05" }, // IPt
36
+ ADVANCED_TOOL_USE: {
37
+ featureKey: "tool_search",
38
+ header: "advanced-tool-use-2025-11-20",
39
+ }, // p2r
40
+ TOOL_SEARCH: {
41
+ featureKey: "tool_search",
42
+ header: "tool-search-tool-2025-10-19",
43
+ }, // xPt
44
+ EFFORT: { featureKey: "effort", header: "effort-2025-11-24" }, // Wnt
45
+ TASK_BUDGETS: {
46
+ featureKey: "task_budgets",
47
+ header: "task-budgets-2026-03-13",
48
+ }, // lAn
49
+ PROMPT_CACHING_SCOPE: {
50
+ featureKey: "prompt_caching_scope",
51
+ header: "prompt-caching-scope-2026-01-05",
52
+ }, // qnt
53
+ EXTENDED_CACHE_TTL: {
54
+ featureKey: "extended_cache_ttl",
55
+ header: "extended-cache-ttl-2025-04-11",
56
+ }, // J2e
57
+ SPEED: { featureKey: "speed", header: "fast-mode-2026-02-01" }, // Vnt
58
+ REDACT_THINKING: {
59
+ featureKey: "redact_thinking",
60
+ header: "redact-thinking-2026-02-12",
61
+ }, // kPt
62
+ THINKING_TOKEN_COUNT: {
63
+ featureKey: "thinking_token_count",
64
+ header: "thinking-token-count-2026-05-13",
65
+ }, // cAn
66
+ NARRATION_SUMMARIES: {
67
+ featureKey: "narration_summaries",
68
+ header: "summarize-connector-text-2026-03-13",
69
+ }, // RPt
70
+ AFK_MODE: { featureKey: "afk_mode", header: "afk-mode-2026-01-31" }, // T0
71
+ ADVISOR_TOOL: {
72
+ featureKey: "advisor_tool",
73
+ header: "advisor-tool-2026-03-01",
74
+ }, // f2r
75
+ CACHE_DIAGNOSIS: {
76
+ featureKey: "cache_diagnosis",
77
+ header: "cache-diagnosis-2026-04-07",
78
+ }, // fye
79
+ CONTEXT_HINT: {
80
+ featureKey: "context_hint",
81
+ header: "context-hint-2026-04-09",
82
+ }, // m2r
83
+ MCP_SERVERS: {
84
+ featureKey: "mcp_servers",
85
+ header: "mcp-servers-2025-12-04",
86
+ }, // g2r
87
+ FILES_API: { featureKey: "files_api", header: "files-api-2025-04-14" }, // h2r
88
+ ENVIRONMENTS: {
89
+ featureKey: "environments",
90
+ header: "environments-2025-11-01",
91
+ }, // y2r
92
+ CCR_BYOC: { featureKey: "ccr_byoc", header: "ccr-byoc-2025-07-29" }, // _2r
93
+ MID_CONVERSATION_SYSTEM: {
94
+ featureKey: "mid_conversation_system",
95
+ header: "mid-conversation-system-2026-04-07",
96
+ }, // jY
97
+ SERVER_SIDE_FALLBACK: {
98
+ featureKey: "server_side_fallback",
99
+ header: "server-side-fallback-2026-06-01",
100
+ }, // r1
101
+ FALLBACK_CREDIT: {
102
+ featureKey: "fallback_credit",
103
+ header: "fallback-credit-2026-06-01",
104
+ }, // o1
105
+ } satisfies Record<string, BetaRegistryEntry>);
106
+
107
+ // Reserved for later work packages; upstream `Pvi` third-party filtering.
108
+ export const THIRD_PARTY_ALLOWED_BETAS: ReadonlySet<string> = Object.freeze(
109
+ new Set([
110
+ BETA_REGISTRY.CLAUDE_CODE.header,
111
+ BETA_REGISTRY.INTERLEAVED_THINKING.header,
112
+ BETA_REGISTRY.LONG_CONTEXT.header,
113
+ BETA_REGISTRY.CONTEXT_MANAGEMENT.header,
114
+ BETA_REGISTRY.STRUCTURED_OUTPUTS.header,
115
+ BETA_REGISTRY.WEB_SEARCH.header,
116
+ BETA_REGISTRY.EFFORT.header,
117
+ BETA_REGISTRY.TOOL_SEARCH.header,
118
+ BETA_REGISTRY.AFK_MODE.header,
119
+ BETA_REGISTRY.FALLBACK_CREDIT.header,
120
+ ]),
121
+ );
122
+
123
+ // Reserved for later work packages; upstream `S2r` provider filtering.
124
+ export const BEDROCK_UNSUPPORTED_BETAS: ReadonlySet<string> = Object.freeze(
125
+ new Set([
126
+ BETA_REGISTRY.INTERLEAVED_THINKING.header,
127
+ BETA_REGISTRY.LONG_CONTEXT.header,
128
+ BETA_REGISTRY.TOOL_SEARCH.header,
129
+ ]),
130
+ );
131
+
132
+ // Reserved for later work packages; upstream `E2r` count-tokens selection.
133
+ export const COUNT_TOKENS_BETAS: ReadonlySet<string> = Object.freeze(
134
+ new Set([
135
+ BETA_REGISTRY.CLAUDE_CODE.header,
136
+ BETA_REGISTRY.INTERLEAVED_THINKING.header,
137
+ BETA_REGISTRY.CONTEXT_MANAGEMENT.header,
138
+ BETA_REGISTRY.OAUTH_AUTH.header,
139
+ ]),
140
+ );