@tormentalabs/claude-code-wire-compat 0.4.0 → 0.5.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 CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.5.0] - 2026-08-16
6
+
7
+ ### Added
8
+
9
+ - **Model-query surface**, fourteen exports from the package entry point:
10
+ the generic `modelCapability(model, capability, profile)`, which reads the
11
+ verbatim upstream capability string off the active catalogue, plus the named
12
+ identity predicates `isOpus46Model`, `isOpus47Model`, `isOpus48Model`,
13
+ `isSonnet46Model`, `isFable5Model`, `isMythos5Model`, `isHaikuModel`,
14
+ `isClaude3Model`, `isAdaptiveThinkingModel`, `hasOneMillionContext`,
15
+ `isEligibleFor1MContext`, `supportsStructuredOutputs` and
16
+ `supportsWebSearch`.
17
+
18
+ The predicates are written over the existing `normalizeModelId` /
19
+ `modelFamilyOf` pair rather than over new family regexes, so a model id
20
+ classifies the same way here as it does everywhere else in the package. They
21
+ answer `false` for an empty or non-string id instead of throwing.
22
+
23
+ Motivation: consumers were re-deriving model identity from ad hoc substring
24
+ matches, which drifts from the catalogue the package already carries.
25
+
26
+ - **`normalizeModelId` accepts dotted model version ids.** A digit-dot-digit
27
+ run is rewritten to the hyphenated wire form before the upstream branch
28
+ ladder, so `claude-opus-4.7` classifies identically to `claude-opus-4-7`.
29
+
30
+ Compatibility: ids without a dotted version (`gpt-4o`, the empty string,
31
+ `vendor.example/model-x`) normalise byte-identically to before.
32
+
33
+ - **Beta registries are public**: `BETA_REGISTRY` (28 entries),
34
+ `BETA_REGISTRY_2_1_233` (31 entries) and `TOKEN_COUNTING_BETA`. They are
35
+ protocol constants transcribed from the genuine client, so a consumer that
36
+ reads a beta header off the registry cannot drift from the package that
37
+ emits it. Reading a header is not emitting one — composition, gating and
38
+ push order stay inside the package, and the policy sets
39
+ (`THIRD_PARTY_ALLOWED_BETAS`, `BEDROCK_UNSUPPORTED_BETAS`,
40
+ `COUNT_TOKENS_BETAS`) remain private.
41
+
42
+ ### Documentation
43
+
44
+ - **Endpoint URL contract** (README, "Endpoint URL and custom base URLs"):
45
+ `built.url` is the profile's pinned, literal-typed endpoint. A host with a
46
+ custom base substitutes protocol, hostname and port while preserving the
47
+ package's `pathname` and `search`. The input deliberately gains no `baseUrl`
48
+ field — widening the literal `url` type is a breaking change for consumers
49
+ that pin the endpoint, the field would duplicate what `URL` already does,
50
+ and the only observed consumer case is an origin override.
51
+
52
+ No breaking changes: every surface above is additive, and the golden fixtures
53
+ re-seal to the same bytes.
54
+
5
55
  ## [0.4.0] - 2026-08-16
6
56
 
7
57
  ### Added
package/README.md CHANGED
@@ -30,6 +30,38 @@ import { CLAUDE_CODE_2_1_233_PROFILE } from "@tormentalabs/claude-code-wire-comp
30
30
 
31
31
  The fail-closed rule is unchanged: only these exported singletons are accepted. Any other object, even a structurally identical clone, is rejected with `ClaudeCodeWireError` code `INVALID_INPUT`. This prevents callers from substituting an unpinned protocol profile.
32
32
 
33
+ ## Endpoint URL and custom base URLs
34
+
35
+ `built.url` is the **pinned endpoint of the profile**, not a suggestion. It is literal-typed, so the type is part of the contract:
36
+
37
+ - `buildClaudeCodeRequest` → `"https://api.anthropic.com/v1/messages?beta=true"`
38
+ - `buildClaudeCodeCountTokensRequest` → `"https://api.anthropic.com/v1/messages/count_tokens?beta=true"`
39
+
40
+ A host that talks to a proxy, a gateway, or a regional endpoint **overrides the origin and nothing else**: replace protocol, hostname and port; keep the package's `pathname` and `search` verbatim. The `?beta=true` query and the `/v1/messages` path are wire contract — dropping either changes what the server does, and the golden fixtures no longer describe the request that was sent.
41
+
42
+ ```ts
43
+ const built = await buildClaudeCodeRequest(input);
44
+
45
+ // Origin override. `pathname` and `search` come from the package, untouched.
46
+ const target = new URL(built.url);
47
+ const base = new URL(hostBaseUrl); // whatever the host resolved, e.g. from its own config
48
+ target.protocol = base.protocol;
49
+ target.hostname = base.hostname;
50
+ target.port = base.port;
51
+
52
+ await fetch(target, {
53
+ method: built.method,
54
+ headers: built.headers,
55
+ body: built.body,
56
+ });
57
+ ```
58
+
59
+ **The input will not gain a `baseUrl` field.** Three reasons, recorded so the request does not come back:
60
+
61
+ 1. `BuiltClaudeCodeRequest["url"]` is a string literal type. Widening it to `string` to accommodate an arbitrary base is a breaking change at the type level for every consumer that pins the endpoint.
62
+ 2. Speculative surface is not added to this package. A host that has a base URL already has a URL library; a package field would be a second way to do the same thing, with a validation and normalisation burden this package would then own.
63
+ 3. The only real consumer case observed is an origin override, which the four lines above express exactly — including the case where the base URL carries a path prefix, which a naive `baseUrl + pathname` concatenation gets wrong.
64
+
33
65
  ## Protocol documentation
34
66
 
35
67
  The wire contract this package pins was reverse engineered before it was
package/dist/index.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Public entry point for the Claude Code wire compatibility package.
3
3
  *
4
- * Only the surfaces listed below are public. The Wave 2 implementation
5
- * Internal protocol modules remain private; only the documented builder and
6
- * parser are exported here.
4
+ * Only the surfaces listed below are public. Internal protocol modules remain
5
+ * private; the documented builder, parser, anti-verbosity helpers, read-only
6
+ * model queries and the transcribed beta registries are exported here, and
7
+ * nothing else.
7
8
  *
8
9
  * Importing this module has no side effects. It reads no environment, opens
9
10
  * no network connection, touches no clock or random source, and holds no
@@ -12,7 +13,11 @@
12
13
  export type { AntiVerbosityPolicy, AntiVerbositySection, BuiltClaudeCodeCountTokensRequest, BuiltClaudeCodeRequest, ClaudeCodeBetaOverrides, ClaudeCodeBetaPolicy, ClaudeCodeCapabilities, ClaudeCodeCapabilityDecisions, ClaudeCodeCatalogueEntry, ClaudeCodeEffort, ClaudeCodeExtraHeaderPolicy, ClaudeCodeMetadataOverrides, ClaudeCodeModelFamily, ClaudeCodeProtocolProfile, ClaudeCodeCountTokensInput, ClaudeCodeRequestInput, ClaudeCodeRuntimeIdentity, ClaudeCodeWireErrorCode, HeaderPair, JsonPrimitive, JsonValue, Message, MessageContent, RedactedRequestEvidence, SystemInput, TextBlock, ThinkingDisplay, ToolDefinition, ToolResultBlock, ToolUseBlock, } from "./contracts.js";
13
14
  export { ClaudeCodeWireError } from "./contracts.js";
14
15
  export { DEFAULT_ANTI_VERBOSITY_POLICY, antiVerbosityText, selectAntiVerbositySection, } from "./anti-verbosity.js";
16
+ export { BETA_REGISTRY } from "./beta-registry.js";
15
17
  export { buildClaudeCodeCountTokensRequest, buildClaudeCodeRequest, parseBuiltClaudeCodeRequest, } from "./build-request.js";
18
+ export { TOKEN_COUNTING_BETA } from "./count-tokens.js";
19
+ export { hasOneMillionContext, isAdaptiveThinkingModel, isClaude3Model, isEligibleFor1MContext, isFable5Model, isHaikuModel, isMythos5Model, isOpus46Model, isOpus47Model, isOpus48Model, isSonnet46Model, modelCapability, supportsStructuredOutputs, supportsWebSearch, } from "./model-queries.js";
20
+ export { BETA_REGISTRY_2_1_233 } from "./profiles/beta-registry-2.1.233.js";
16
21
  export { CLAUDE_CODE_2_1_195_PROFILE } from "./profiles/claude-code-2.1.195.js";
17
22
  export { CLAUDE_CODE_2_1_233_PROFILE } from "./profiles/claude-code-2.1.233.js";
18
23
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AAEH,YAAY,EACV,mBAAmB,EACnB,oBAAoB,EACpB,iCAAiC,EACjC,sBAAsB,EACtB,uBAAuB,EACvB,oBAAoB,EACpB,sBAAsB,EACtB,6BAA6B,EAC7B,wBAAwB,EACxB,gBAAgB,EAChB,2BAA2B,EAC3B,2BAA2B,EAC3B,qBAAqB,EACrB,yBAAyB,EACzB,0BAA0B,EAC1B,sBAAsB,EACtB,yBAAyB,EACzB,uBAAuB,EACvB,UAAU,EACV,aAAa,EACb,SAAS,EACT,OAAO,EACP,cAAc,EACd,uBAAuB,EACvB,WAAW,EACX,SAAS,EACT,eAAe,EACf,cAAc,EACd,eAAe,EACf,YAAY,GACb,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAErD,OAAO,EACL,6BAA6B,EAC7B,iBAAiB,EACjB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,iCAAiC,EACjC,sBAAsB,EACtB,2BAA2B,GAC5B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,2BAA2B,EAAE,MAAM,mCAAmC,CAAC;AAChF,OAAO,EAAE,2BAA2B,EAAE,MAAM,mCAAmC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AAEH,YAAY,EACV,mBAAmB,EACnB,oBAAoB,EACpB,iCAAiC,EACjC,sBAAsB,EACtB,uBAAuB,EACvB,oBAAoB,EACpB,sBAAsB,EACtB,6BAA6B,EAC7B,wBAAwB,EACxB,gBAAgB,EAChB,2BAA2B,EAC3B,2BAA2B,EAC3B,qBAAqB,EACrB,yBAAyB,EACzB,0BAA0B,EAC1B,sBAAsB,EACtB,yBAAyB,EACzB,uBAAuB,EACvB,UAAU,EACV,aAAa,EACb,SAAS,EACT,OAAO,EACP,cAAc,EACd,uBAAuB,EACvB,WAAW,EACX,SAAS,EACT,eAAe,EACf,cAAc,EACd,eAAe,EACf,YAAY,GACb,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAErD,OAAO,EACL,6BAA6B,EAC7B,iBAAiB,EACjB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EACL,iCAAiC,EACjC,sBAAsB,EACtB,2BAA2B,GAC5B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,cAAc,EACd,sBAAsB,EACtB,aAAa,EACb,YAAY,EACZ,cAAc,EACd,aAAa,EACb,aAAa,EACb,aAAa,EACb,eAAe,EACf,eAAe,EACf,yBAAyB,EACzB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAC5E,OAAO,EAAE,2BAA2B,EAAE,MAAM,mCAAmC,CAAC;AAChF,OAAO,EAAE,2BAA2B,EAAE,MAAM,mCAAmC,CAAC"}
package/dist/index.js CHANGED
@@ -1,7 +1,11 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-or-later
2
2
  export { ClaudeCodeWireError } from "./contracts.js";
3
3
  export { DEFAULT_ANTI_VERBOSITY_POLICY, antiVerbosityText, selectAntiVerbositySection, } from "./anti-verbosity.js";
4
+ export { BETA_REGISTRY } from "./beta-registry.js";
4
5
  export { buildClaudeCodeCountTokensRequest, buildClaudeCodeRequest, parseBuiltClaudeCodeRequest, } from "./build-request.js";
6
+ export { TOKEN_COUNTING_BETA } from "./count-tokens.js";
7
+ export { hasOneMillionContext, isAdaptiveThinkingModel, isClaude3Model, isEligibleFor1MContext, isFable5Model, isHaikuModel, isMythos5Model, isOpus46Model, isOpus47Model, isOpus48Model, isSonnet46Model, modelCapability, supportsStructuredOutputs, supportsWebSearch, } from "./model-queries.js";
8
+ export { BETA_REGISTRY_2_1_233 } from "./profiles/beta-registry-2.1.233.js";
5
9
  export { CLAUDE_CODE_2_1_195_PROFILE } from "./profiles/claude-code-2.1.195.js";
6
10
  export { CLAUDE_CODE_2_1_233_PROFILE } from "./profiles/claude-code-2.1.233.js";
7
11
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4CAA4C;AA+C5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAErD,OAAO,EACL,6BAA6B,EAC7B,iBAAiB,EACjB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,iCAAiC,EACjC,sBAAsB,EACtB,2BAA2B,GAC5B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,2BAA2B,EAAE,MAAM,mCAAmC,CAAC;AAChF,OAAO,EAAE,2BAA2B,EAAE,MAAM,mCAAmC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAgD5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAErD,OAAO,EACL,6BAA6B,EAC7B,iBAAiB,EACjB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EACL,iCAAiC,EACjC,sBAAsB,EACtB,2BAA2B,GAC5B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,cAAc,EACd,sBAAsB,EACtB,aAAa,EACb,YAAY,EACZ,cAAc,EACd,aAAa,EACb,aAAa,EACb,aAAa,EACb,eAAe,EACf,eAAe,EACf,yBAAyB,EACzB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAC5E,OAAO,EAAE,2BAA2B,EAAE,MAAM,mCAAmC,CAAC;AAChF,OAAO,EAAE,2BAA2B,EAAE,MAAM,mCAAmC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"model-identity.d.ts","sourceRoot":"","sources":["../src/model-identity.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAE5D,qDAAqD;AACrD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,qDAAqD;AACrD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAqBtD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,qBAAqB,CAOzE"}
1
+ {"version":3,"file":"model-identity.d.ts","sourceRoot":"","sources":["../src/model-identity.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAE5D,qDAAqD;AACrD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvD;AAgBD,qDAAqD;AACrD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAqBtD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,qBAAqB,CAOzE"}
@@ -3,9 +3,22 @@
3
3
  export function stripModelMarkers(model) {
4
4
  return model.replace(/\[(1|2)m\]/gi, "");
5
5
  }
6
+ /**
7
+ * Rewrites dotted version separators to the hyphenated form upstream uses on
8
+ * the wire (`claude-opus-4.7` -> `claude-opus-4-7`). Only a digit-dot-digit
9
+ * run is rewritten, so ids with no dotted version (`gpt-4o`, `""`) and
10
+ * unrelated dots are left byte-identical.
11
+ *
12
+ * The plugin performs the same rewrite at its wire seam
13
+ * (`lib/mimicry/wire-compat.mjs`); the plugin's own family predicates accept
14
+ * `[._-]` interchangeably, so this keeps package classification in parity.
15
+ */
16
+ function dottedToDashedVersion(model) {
17
+ return model.replace(/(\d)\.(\d)/g, "$1-$2");
18
+ }
6
19
  /** Ports upstream `$_` (binary offset 226639025). */
7
20
  export function normalizeModelId(model) {
8
- model = model.toLowerCase();
21
+ model = dottedToDashedVersion(model.toLowerCase());
9
22
  if (model.includes("claude-fable-5"))
10
23
  return "claude-fable-5";
11
24
  if (model.includes("claude-mythos-5"))
@@ -1 +1 @@
1
- {"version":3,"file":"model-identity.js","sourceRoot":"","sources":["../src/model-identity.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAI5C,qDAAqD;AACrD,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,OAAO,KAAK,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;AAC3C,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IAC5B,IAAI,KAAK,CAAC,QAAQ,CAAC,gBAAgB,CAAC;QAAE,OAAO,gBAAgB,CAAC;IAC9D,IAAI,KAAK,CAAC,QAAQ,CAAC,iBAAiB,CAAC;QAAE,OAAO,iBAAiB,CAAC;IAChE,IAAI,KAAK,CAAC,QAAQ,CAAC,iBAAiB,CAAC;QAAE,OAAO,iBAAiB,CAAC;IAChE,IAAI,KAAK,CAAC,QAAQ,CAAC,iBAAiB,CAAC;QAAE,OAAO,iBAAiB,CAAC;IAChE,IAAI,KAAK,CAAC,QAAQ,CAAC,iBAAiB,CAAC;QAAE,OAAO,iBAAiB,CAAC;IAChE,IAAI,KAAK,CAAC,QAAQ,CAAC,iBAAiB,CAAC;QAAE,OAAO,iBAAiB,CAAC;IAChE,IAAI,KAAK,CAAC,QAAQ,CAAC,iBAAiB,CAAC;QAAE,OAAO,iBAAiB,CAAC;IAChE,IAAI,4BAA4B,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,iBAAiB,CAAC;IACvE,IAAI,KAAK,CAAC,QAAQ,CAAC,mBAAmB,CAAC;QAAE,OAAO,mBAAmB,CAAC;IACpE,IAAI,KAAK,CAAC,QAAQ,CAAC,mBAAmB,CAAC;QAAE,OAAO,mBAAmB,CAAC;IACpE,IAAI,8BAA8B,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,mBAAmB,CAAC;IAC3E,IAAI,KAAK,CAAC,QAAQ,CAAC,kBAAkB,CAAC;QAAE,OAAO,kBAAkB,CAAC;IAClE,IAAI,KAAK,CAAC,QAAQ,CAAC,mBAAmB,CAAC;QAAE,OAAO,mBAAmB,CAAC;IACpE,IAAI,KAAK,CAAC,QAAQ,CAAC,mBAAmB,CAAC;QAAE,OAAO,mBAAmB,CAAC;IACpE,IAAI,KAAK,CAAC,QAAQ,CAAC,kBAAkB,CAAC;QAAE,OAAO,kBAAkB,CAAC;IAClE,IAAI,KAAK,CAAC,QAAQ,CAAC,eAAe,CAAC;QAAE,OAAO,eAAe,CAAC;IAC5D,IAAI,KAAK,CAAC,QAAQ,CAAC,iBAAiB,CAAC;QAAE,OAAO,iBAAiB,CAAC;IAChE,IAAI,KAAK,CAAC,QAAQ,CAAC,gBAAgB,CAAC;QAAE,OAAO,gBAAgB,CAAC;IAC9D,OAAO,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;AACtC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,YAAoB;IAChD,IAAI,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAC;IACnD,IAAI,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC;IACrD,IAAI,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAC;IACnD,IAAI,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC;IACrD,IAAI,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC;IACjD,OAAO,SAAS,CAAC;AACnB,CAAC"}
1
+ {"version":3,"file":"model-identity.js","sourceRoot":"","sources":["../src/model-identity.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAI5C,qDAAqD;AACrD,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,OAAO,KAAK,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;AAC3C,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,qBAAqB,CAAC,KAAa;IAC1C,OAAO,KAAK,CAAC,OAAO,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;AAC/C,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,KAAK,GAAG,qBAAqB,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;IACnD,IAAI,KAAK,CAAC,QAAQ,CAAC,gBAAgB,CAAC;QAAE,OAAO,gBAAgB,CAAC;IAC9D,IAAI,KAAK,CAAC,QAAQ,CAAC,iBAAiB,CAAC;QAAE,OAAO,iBAAiB,CAAC;IAChE,IAAI,KAAK,CAAC,QAAQ,CAAC,iBAAiB,CAAC;QAAE,OAAO,iBAAiB,CAAC;IAChE,IAAI,KAAK,CAAC,QAAQ,CAAC,iBAAiB,CAAC;QAAE,OAAO,iBAAiB,CAAC;IAChE,IAAI,KAAK,CAAC,QAAQ,CAAC,iBAAiB,CAAC;QAAE,OAAO,iBAAiB,CAAC;IAChE,IAAI,KAAK,CAAC,QAAQ,CAAC,iBAAiB,CAAC;QAAE,OAAO,iBAAiB,CAAC;IAChE,IAAI,KAAK,CAAC,QAAQ,CAAC,iBAAiB,CAAC;QAAE,OAAO,iBAAiB,CAAC;IAChE,IAAI,4BAA4B,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,iBAAiB,CAAC;IACvE,IAAI,KAAK,CAAC,QAAQ,CAAC,mBAAmB,CAAC;QAAE,OAAO,mBAAmB,CAAC;IACpE,IAAI,KAAK,CAAC,QAAQ,CAAC,mBAAmB,CAAC;QAAE,OAAO,mBAAmB,CAAC;IACpE,IAAI,8BAA8B,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,mBAAmB,CAAC;IAC3E,IAAI,KAAK,CAAC,QAAQ,CAAC,kBAAkB,CAAC;QAAE,OAAO,kBAAkB,CAAC;IAClE,IAAI,KAAK,CAAC,QAAQ,CAAC,mBAAmB,CAAC;QAAE,OAAO,mBAAmB,CAAC;IACpE,IAAI,KAAK,CAAC,QAAQ,CAAC,mBAAmB,CAAC;QAAE,OAAO,mBAAmB,CAAC;IACpE,IAAI,KAAK,CAAC,QAAQ,CAAC,kBAAkB,CAAC;QAAE,OAAO,kBAAkB,CAAC;IAClE,IAAI,KAAK,CAAC,QAAQ,CAAC,eAAe,CAAC;QAAE,OAAO,eAAe,CAAC;IAC5D,IAAI,KAAK,CAAC,QAAQ,CAAC,iBAAiB,CAAC;QAAE,OAAO,iBAAiB,CAAC;IAChE,IAAI,KAAK,CAAC,QAAQ,CAAC,gBAAgB,CAAC;QAAE,OAAO,gBAAgB,CAAC;IAC9D,OAAO,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;AACtC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,YAAoB;IAChD,IAAI,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAC;IACnD,IAAI,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC;IACrD,IAAI,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAC;IACnD,IAAI,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC;IACrD,IAAI,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC;IACjD,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -0,0 +1,77 @@
1
+ import type { ClaudeCodeProtocolProfile } from "./contracts.js";
2
+ /**
3
+ * Whether the active profile's catalogue records `capability` for `model`.
4
+ *
5
+ * `capability` is a verbatim upstream capability string. Ids the profile does
6
+ * not catalogue answer `false` for every capability: the catalogue is the only
7
+ * evidence this query consults, and absence of evidence is reported as
8
+ * absence. Callers wanting the derived nine-boolean view -- which falls back
9
+ * to the ported predicates for uncatalogued ids -- want `resolveModel`.
10
+ */
11
+ export declare function modelCapability(model: string, capability: string, profile?: ClaudeCodeProtocolProfile): boolean;
12
+ /** Whether `model` normalizes to `claude-opus-4-6`. */
13
+ export declare function isOpus46Model(model: string): boolean;
14
+ /** Whether `model` normalizes to `claude-opus-4-7`. */
15
+ export declare function isOpus47Model(model: string): boolean;
16
+ /** Whether `model` normalizes to `claude-opus-4-8`. */
17
+ export declare function isOpus48Model(model: string): boolean;
18
+ /** Whether `model` normalizes to `claude-sonnet-4-6`. */
19
+ export declare function isSonnet46Model(model: string): boolean;
20
+ /** Whether `model` normalizes to `claude-fable-5`. */
21
+ export declare function isFable5Model(model: string): boolean;
22
+ /** Whether `model` normalizes to `claude-mythos-5`. */
23
+ export declare function isMythos5Model(model: string): boolean;
24
+ /**
25
+ * Whether `model` belongs to the haiku family.
26
+ *
27
+ * Reuses `modelFamilyOf`, the package's one family classifier, so a new haiku
28
+ * id is classified in exactly one place.
29
+ */
30
+ export declare function isHaikuModel(model: string): boolean;
31
+ /**
32
+ * Whether `model` is a Claude 3 generation id.
33
+ *
34
+ * The `claude-3-` substring test is the same one the ported capability
35
+ * predicates open with (`model-capabilities.ts`), applied to the normalized
36
+ * id, so a Claude 3 id spelled with a dotted version (`claude-3.5-sonnet`)
37
+ * classifies with its hyphenated spelling.
38
+ */
39
+ export declare function isClaude3Model(model: string): boolean;
40
+ /**
41
+ * Whether `model` may receive the 1M-context beta.
42
+ *
43
+ * Three sources, in order: an explicit 1M marker in the id, the active
44
+ * profile's `context.supports1mBeta`, and -- for ids the profile does not
45
+ * catalogue -- the ported family set (`claude-sonnet-4*`, Opus 4.6/4.7/4.8).
46
+ */
47
+ export declare function isEligibleFor1MContext(model: string, profile?: ClaudeCodeProtocolProfile): boolean;
48
+ /**
49
+ * Whether `model` names 1M context explicitly and therefore always uses it.
50
+ *
51
+ * Marker-only by design. The catalogue's `context.native1m` is a DIFFERENT
52
+ * question -- the model's native window -- and consulting it here would make
53
+ * every natively-1M id answer true, which is not what a static "always send
54
+ * 1M" gate means. Use `modelContextWindow`-shaped catalogue reads for that.
55
+ */
56
+ export declare function hasOneMillionContext(model: string): boolean;
57
+ /**
58
+ * Whether `model` supports the structured-outputs beta.
59
+ *
60
+ * Delegates to the predicate ported from the genuine client (`j4e`), which is
61
+ * narrower and better evidenced than a family-shaped heuristic.
62
+ */
63
+ export declare function supportsStructuredOutputs(model: string): boolean;
64
+ /** Whether `model` supports the web-search tool. */
65
+ export declare function supportsWebSearch(model: string): boolean;
66
+ /**
67
+ * Whether `model` uses adaptive thinking (`{type: "adaptive"}`) instead of a
68
+ * manual `budget_tokens`.
69
+ *
70
+ * The union of the named family predicates, not a catalogue read: this gates
71
+ * the shape of the emitted `thinking` block, and an uncatalogued id must not
72
+ * inherit adaptive thinking from the permissive capability fallback. Ask
73
+ * `modelCapability(model, "adaptive_thinking", profile)` for the catalogue's
74
+ * answer.
75
+ */
76
+ export declare function isAdaptiveThinkingModel(model: string): boolean;
77
+ //# sourceMappingURL=model-queries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-queries.d.ts","sourceRoot":"","sources":["../src/model-queries.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AA4EhE;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,yBAAuD,GAC/D,OAAO,CAcT;AAED,uDAAuD;AACvD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,uDAAuD;AACvD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,uDAAuD;AACvD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,yDAAyD;AACzD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED,sDAAsD;AACtD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,uDAAuD;AACvD,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAErD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAGnD;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAErD;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,yBAAuD,GAC/D,OAAO,CAkBT;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAGhE;AAED,oDAAoD;AACpD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAKxD;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAS9D"}
@@ -0,0 +1,205 @@
1
+ // SPDX-License-Identifier: GPL-3.0-or-later
2
+ import { supportsStructuredOutputs as portedStructuredOutputs } from "./model-capabilities.js";
3
+ import { modelFamilyOf, normalizeModelId } from "./model-identity.js";
4
+ import { CLAUDE_CODE_2_1_195_PROFILE } from "./profiles/claude-code-2.1.195.js";
5
+ /*
6
+ * Read-only model queries.
7
+ *
8
+ * DESIGN DECISION (Phase 1.1). Two surfaces, deliberately separate:
9
+ *
10
+ * 1. `modelCapability(model, capability, profile)` -- the GENERIC query. The
11
+ * catalogue is the source of truth: it normalizes the id, looks the entry
12
+ * up in `profile.supportedModels` and asks whether the verbatim upstream
13
+ * capability string is present. It invents nothing, maps nothing, and
14
+ * therefore answers for capability strings this package does not model as
15
+ * a `ClaudeCodeCapabilities` field (`fast_mode`, `lean_prompt`,
16
+ * `fable_5_mitigations`, `mid_conv_system`, ...) as readily as for the
17
+ * six that it does.
18
+ *
19
+ * 2. The NAMED family/version predicates below -- `isOpus47Model` and
20
+ * friends. A family is not a catalogue capability; it is an identity
21
+ * question. They are therefore written over `normalizeModelId` and
22
+ * `modelFamilyOf`, NOT over a new family regex and NOT over the
23
+ * catalogue. Consequence, and it is intended: an id normalizing to
24
+ * `claude-opus-4-7` answers `isOpus47Model` true whether or not the
25
+ * active profile catalogues it.
26
+ *
27
+ * Do not fold (2) into (1). Asking `modelCapability(model, "adaptive_thinking")`
28
+ * and asking `isAdaptiveThinkingModel(model)` are different questions with
29
+ * different answers for ids the active profile does not catalogue, and both
30
+ * questions have callers.
31
+ *
32
+ * INVALID INPUT. Every predicate here returns `false` for a non-string or an
33
+ * empty id rather than throwing. This departs from `resolveModel`
34
+ * (`ClaudeCodeWireError("INVALID_INPUT")`) on purpose: these are predicates,
35
+ * their upstream counterparts are total functions returning `false` on a
36
+ * falsy model, and a predicate that throws cannot be used in the boolean
37
+ * position its callers put it in.
38
+ *
39
+ * RUNTIME NEUTRALITY. No builtins, no clock, no randomness, no I/O.
40
+ */
41
+ /** Lowercased + dotted-to-dashed id, or `null` for input no predicate can answer for. */
42
+ function normalizedOrNull(model) {
43
+ if (typeof model !== "string" || model.length === 0) {
44
+ return null;
45
+ }
46
+ return normalizeModelId(model);
47
+ }
48
+ /**
49
+ * Explicit 1M markers a caller can spell into the model id.
50
+ *
51
+ * `\[1m\]` is deliberately absent: it marks a request-time context selection,
52
+ * not an always-1M id, and the upstream marker check does not accept it.
53
+ */
54
+ const ONE_MILLION_MARKER_RE = /(^|[-_ ])1m($|[-_ ])|context[-_]?1m/iu;
55
+ /** As `ONE_MILLION_MARKER_RE`, plus the bracketed request-time marker. */
56
+ const ONE_MILLION_ELIGIBLE_MARKER_RE = /(^|[-_ ])1m($|[-_ ])|context[-_]?1m|\[1m\]/iu;
57
+ /**
58
+ * Vendor tokens the upstream web-search gate accepts. Transcribed as a token
59
+ * list rather than a regex so it stays a data question, not a pattern-matching
60
+ * one.
61
+ */
62
+ const WEB_SEARCH_VENDOR_TOKENS = Object.freeze([
63
+ "claude",
64
+ "sonnet",
65
+ "opus",
66
+ "haiku",
67
+ "gpt",
68
+ "gemini",
69
+ ]);
70
+ /**
71
+ * Whether the active profile's catalogue records `capability` for `model`.
72
+ *
73
+ * `capability` is a verbatim upstream capability string. Ids the profile does
74
+ * not catalogue answer `false` for every capability: the catalogue is the only
75
+ * evidence this query consults, and absence of evidence is reported as
76
+ * absence. Callers wanting the derived nine-boolean view -- which falls back
77
+ * to the ported predicates for uncatalogued ids -- want `resolveModel`.
78
+ */
79
+ export function modelCapability(model, capability, profile = CLAUDE_CODE_2_1_195_PROFILE) {
80
+ const id = normalizedOrNull(model);
81
+ if (id === null ||
82
+ typeof capability !== "string" ||
83
+ capability.length === 0) {
84
+ return false;
85
+ }
86
+ const entry = profile.supportedModels[id];
87
+ if (entry === undefined) {
88
+ return false;
89
+ }
90
+ return entry.capabilities.includes(capability);
91
+ }
92
+ /** Whether `model` normalizes to `claude-opus-4-6`. */
93
+ export function isOpus46Model(model) {
94
+ return normalizedOrNull(model) === "claude-opus-4-6";
95
+ }
96
+ /** Whether `model` normalizes to `claude-opus-4-7`. */
97
+ export function isOpus47Model(model) {
98
+ return normalizedOrNull(model) === "claude-opus-4-7";
99
+ }
100
+ /** Whether `model` normalizes to `claude-opus-4-8`. */
101
+ export function isOpus48Model(model) {
102
+ return normalizedOrNull(model) === "claude-opus-4-8";
103
+ }
104
+ /** Whether `model` normalizes to `claude-sonnet-4-6`. */
105
+ export function isSonnet46Model(model) {
106
+ return normalizedOrNull(model) === "claude-sonnet-4-6";
107
+ }
108
+ /** Whether `model` normalizes to `claude-fable-5`. */
109
+ export function isFable5Model(model) {
110
+ return normalizedOrNull(model) === "claude-fable-5";
111
+ }
112
+ /** Whether `model` normalizes to `claude-mythos-5`. */
113
+ export function isMythos5Model(model) {
114
+ return normalizedOrNull(model) === "claude-mythos-5";
115
+ }
116
+ /**
117
+ * Whether `model` belongs to the haiku family.
118
+ *
119
+ * Reuses `modelFamilyOf`, the package's one family classifier, so a new haiku
120
+ * id is classified in exactly one place.
121
+ */
122
+ export function isHaikuModel(model) {
123
+ const id = normalizedOrNull(model);
124
+ return id !== null && modelFamilyOf(id) === "haiku";
125
+ }
126
+ /**
127
+ * Whether `model` is a Claude 3 generation id.
128
+ *
129
+ * The `claude-3-` substring test is the same one the ported capability
130
+ * predicates open with (`model-capabilities.ts`), applied to the normalized
131
+ * id, so a Claude 3 id spelled with a dotted version (`claude-3.5-sonnet`)
132
+ * classifies with its hyphenated spelling.
133
+ */
134
+ export function isClaude3Model(model) {
135
+ return normalizedOrNull(model)?.includes("claude-3-") ?? false;
136
+ }
137
+ /**
138
+ * Whether `model` may receive the 1M-context beta.
139
+ *
140
+ * Three sources, in order: an explicit 1M marker in the id, the active
141
+ * profile's `context.supports1mBeta`, and -- for ids the profile does not
142
+ * catalogue -- the ported family set (`claude-sonnet-4*`, Opus 4.6/4.7/4.8).
143
+ */
144
+ export function isEligibleFor1MContext(model, profile = CLAUDE_CODE_2_1_195_PROFILE) {
145
+ const id = normalizedOrNull(model);
146
+ if (id === null) {
147
+ return false;
148
+ }
149
+ if (ONE_MILLION_ELIGIBLE_MARKER_RE.test(model)) {
150
+ return true;
151
+ }
152
+ const entry = profile.supportedModels[id];
153
+ if (entry !== undefined) {
154
+ return entry.context?.supports1mBeta === true;
155
+ }
156
+ return (id.startsWith("claude-sonnet-4") ||
157
+ id === "claude-opus-4-6" ||
158
+ id === "claude-opus-4-7" ||
159
+ id === "claude-opus-4-8");
160
+ }
161
+ /**
162
+ * Whether `model` names 1M context explicitly and therefore always uses it.
163
+ *
164
+ * Marker-only by design. The catalogue's `context.native1m` is a DIFFERENT
165
+ * question -- the model's native window -- and consulting it here would make
166
+ * every natively-1M id answer true, which is not what a static "always send
167
+ * 1M" gate means. Use `modelContextWindow`-shaped catalogue reads for that.
168
+ */
169
+ export function hasOneMillionContext(model) {
170
+ return normalizedOrNull(model) !== null && ONE_MILLION_MARKER_RE.test(model);
171
+ }
172
+ /**
173
+ * Whether `model` supports the structured-outputs beta.
174
+ *
175
+ * Delegates to the predicate ported from the genuine client (`j4e`), which is
176
+ * narrower and better evidenced than a family-shaped heuristic.
177
+ */
178
+ export function supportsStructuredOutputs(model) {
179
+ const id = normalizedOrNull(model);
180
+ return id !== null && portedStructuredOutputs(id);
181
+ }
182
+ /** Whether `model` supports the web-search tool. */
183
+ export function supportsWebSearch(model) {
184
+ const id = normalizedOrNull(model);
185
+ return (id !== null && WEB_SEARCH_VENDOR_TOKENS.some((token) => id.includes(token)));
186
+ }
187
+ /**
188
+ * Whether `model` uses adaptive thinking (`{type: "adaptive"}`) instead of a
189
+ * manual `budget_tokens`.
190
+ *
191
+ * The union of the named family predicates, not a catalogue read: this gates
192
+ * the shape of the emitted `thinking` block, and an uncatalogued id must not
193
+ * inherit adaptive thinking from the permissive capability fallback. Ask
194
+ * `modelCapability(model, "adaptive_thinking", profile)` for the catalogue's
195
+ * answer.
196
+ */
197
+ export function isAdaptiveThinkingModel(model) {
198
+ return (isOpus46Model(model) ||
199
+ isOpus47Model(model) ||
200
+ isOpus48Model(model) ||
201
+ isSonnet46Model(model) ||
202
+ isFable5Model(model) ||
203
+ isMythos5Model(model));
204
+ }
205
+ //# sourceMappingURL=model-queries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-queries.js","sourceRoot":"","sources":["../src/model-queries.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.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Runtime-neutral Claude Code Messages wire compatibility primitives",
5
5
  "type": "module",
6
6
  "private": false,
@@ -53,7 +53,6 @@
53
53
  "test:coverage": "vitest run --coverage",
54
54
  "test:pack": "node scripts/verify-packed-consumers.mjs",
55
55
  "pack:check": "npm pack --dry-run && publint && attw --pack . --profile esm-only",
56
- "drift:check": "node scripts/verify-drift.mjs",
57
56
  "fixtures:check": "node scripts/seal-golden-fixtures.mjs --check",
58
57
  "fixtures:seal": "node scripts/seal-golden-fixtures.mjs --write",
59
58
  "extract:profile": "node scripts/extract-upstream-profile.mjs"
package/src/index.ts CHANGED
@@ -3,9 +3,10 @@
3
3
  /**
4
4
  * Public entry point for the Claude Code wire compatibility package.
5
5
  *
6
- * Only the surfaces listed below are public. The Wave 2 implementation
7
- * Internal protocol modules remain private; only the documented builder and
8
- * parser are exported here.
6
+ * Only the surfaces listed below are public. Internal protocol modules remain
7
+ * private; the documented builder, parser, anti-verbosity helpers, read-only
8
+ * model queries and the transcribed beta registries are exported here, and
9
+ * nothing else.
9
10
  *
10
11
  * Importing this module has no side effects. It reads no environment, opens
11
12
  * no network connection, touches no clock or random source, and holds no
@@ -53,11 +54,33 @@ export {
53
54
  selectAntiVerbositySection,
54
55
  } from "./anti-verbosity.js";
55
56
 
57
+ export { BETA_REGISTRY } from "./beta-registry.js";
58
+
56
59
  export {
57
60
  buildClaudeCodeCountTokensRequest,
58
61
  buildClaudeCodeRequest,
59
62
  parseBuiltClaudeCodeRequest,
60
63
  } from "./build-request.js";
61
64
 
65
+ export { TOKEN_COUNTING_BETA } from "./count-tokens.js";
66
+
67
+ export {
68
+ hasOneMillionContext,
69
+ isAdaptiveThinkingModel,
70
+ isClaude3Model,
71
+ isEligibleFor1MContext,
72
+ isFable5Model,
73
+ isHaikuModel,
74
+ isMythos5Model,
75
+ isOpus46Model,
76
+ isOpus47Model,
77
+ isOpus48Model,
78
+ isSonnet46Model,
79
+ modelCapability,
80
+ supportsStructuredOutputs,
81
+ supportsWebSearch,
82
+ } from "./model-queries.js";
83
+
84
+ export { BETA_REGISTRY_2_1_233 } from "./profiles/beta-registry-2.1.233.js";
62
85
  export { CLAUDE_CODE_2_1_195_PROFILE } from "./profiles/claude-code-2.1.195.js";
63
86
  export { CLAUDE_CODE_2_1_233_PROFILE } from "./profiles/claude-code-2.1.233.js";
@@ -7,9 +7,23 @@ export function stripModelMarkers(model: string): string {
7
7
  return model.replace(/\[(1|2)m\]/gi, "");
8
8
  }
9
9
 
10
+ /**
11
+ * Rewrites dotted version separators to the hyphenated form upstream uses on
12
+ * the wire (`claude-opus-4.7` -> `claude-opus-4-7`). Only a digit-dot-digit
13
+ * run is rewritten, so ids with no dotted version (`gpt-4o`, `""`) and
14
+ * unrelated dots are left byte-identical.
15
+ *
16
+ * The plugin performs the same rewrite at its wire seam
17
+ * (`lib/mimicry/wire-compat.mjs`); the plugin's own family predicates accept
18
+ * `[._-]` interchangeably, so this keeps package classification in parity.
19
+ */
20
+ function dottedToDashedVersion(model: string): string {
21
+ return model.replace(/(\d)\.(\d)/g, "$1-$2");
22
+ }
23
+
10
24
  /** Ports upstream `$_` (binary offset 226639025). */
11
25
  export function normalizeModelId(model: string): string {
12
- model = model.toLowerCase();
26
+ model = dottedToDashedVersion(model.toLowerCase());
13
27
  if (model.includes("claude-fable-5")) return "claude-fable-5";
14
28
  if (model.includes("claude-mythos-5")) return "claude-mythos-5";
15
29
  if (model.includes("claude-opus-4-8")) return "claude-opus-4-8";
@@ -0,0 +1,241 @@
1
+ // SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+ import type { ClaudeCodeProtocolProfile } from "./contracts.js";
4
+ import { supportsStructuredOutputs as portedStructuredOutputs } from "./model-capabilities.js";
5
+ import { modelFamilyOf, normalizeModelId } from "./model-identity.js";
6
+ import { CLAUDE_CODE_2_1_195_PROFILE } from "./profiles/claude-code-2.1.195.js";
7
+
8
+ /*
9
+ * Read-only model queries.
10
+ *
11
+ * DESIGN DECISION (Phase 1.1). Two surfaces, deliberately separate:
12
+ *
13
+ * 1. `modelCapability(model, capability, profile)` -- the GENERIC query. The
14
+ * catalogue is the source of truth: it normalizes the id, looks the entry
15
+ * up in `profile.supportedModels` and asks whether the verbatim upstream
16
+ * capability string is present. It invents nothing, maps nothing, and
17
+ * therefore answers for capability strings this package does not model as
18
+ * a `ClaudeCodeCapabilities` field (`fast_mode`, `lean_prompt`,
19
+ * `fable_5_mitigations`, `mid_conv_system`, ...) as readily as for the
20
+ * six that it does.
21
+ *
22
+ * 2. The NAMED family/version predicates below -- `isOpus47Model` and
23
+ * friends. A family is not a catalogue capability; it is an identity
24
+ * question. They are therefore written over `normalizeModelId` and
25
+ * `modelFamilyOf`, NOT over a new family regex and NOT over the
26
+ * catalogue. Consequence, and it is intended: an id normalizing to
27
+ * `claude-opus-4-7` answers `isOpus47Model` true whether or not the
28
+ * active profile catalogues it.
29
+ *
30
+ * Do not fold (2) into (1). Asking `modelCapability(model, "adaptive_thinking")`
31
+ * and asking `isAdaptiveThinkingModel(model)` are different questions with
32
+ * different answers for ids the active profile does not catalogue, and both
33
+ * questions have callers.
34
+ *
35
+ * INVALID INPUT. Every predicate here returns `false` for a non-string or an
36
+ * empty id rather than throwing. This departs from `resolveModel`
37
+ * (`ClaudeCodeWireError("INVALID_INPUT")`) on purpose: these are predicates,
38
+ * their upstream counterparts are total functions returning `false` on a
39
+ * falsy model, and a predicate that throws cannot be used in the boolean
40
+ * position its callers put it in.
41
+ *
42
+ * RUNTIME NEUTRALITY. No builtins, no clock, no randomness, no I/O.
43
+ */
44
+
45
+ /** Lowercased + dotted-to-dashed id, or `null` for input no predicate can answer for. */
46
+ function normalizedOrNull(model: string): string | null {
47
+ if (typeof model !== "string" || model.length === 0) {
48
+ return null;
49
+ }
50
+ return normalizeModelId(model);
51
+ }
52
+
53
+ /**
54
+ * Explicit 1M markers a caller can spell into the model id.
55
+ *
56
+ * `\[1m\]` is deliberately absent: it marks a request-time context selection,
57
+ * not an always-1M id, and the upstream marker check does not accept it.
58
+ */
59
+ const ONE_MILLION_MARKER_RE = /(^|[-_ ])1m($|[-_ ])|context[-_]?1m/iu;
60
+
61
+ /** As `ONE_MILLION_MARKER_RE`, plus the bracketed request-time marker. */
62
+ const ONE_MILLION_ELIGIBLE_MARKER_RE =
63
+ /(^|[-_ ])1m($|[-_ ])|context[-_]?1m|\[1m\]/iu;
64
+
65
+ /**
66
+ * Vendor tokens the upstream web-search gate accepts. Transcribed as a token
67
+ * list rather than a regex so it stays a data question, not a pattern-matching
68
+ * one.
69
+ */
70
+ const WEB_SEARCH_VENDOR_TOKENS: readonly string[] = Object.freeze([
71
+ "claude",
72
+ "sonnet",
73
+ "opus",
74
+ "haiku",
75
+ "gpt",
76
+ "gemini",
77
+ ]);
78
+
79
+ /**
80
+ * Whether the active profile's catalogue records `capability` for `model`.
81
+ *
82
+ * `capability` is a verbatim upstream capability string. Ids the profile does
83
+ * not catalogue answer `false` for every capability: the catalogue is the only
84
+ * evidence this query consults, and absence of evidence is reported as
85
+ * absence. Callers wanting the derived nine-boolean view -- which falls back
86
+ * to the ported predicates for uncatalogued ids -- want `resolveModel`.
87
+ */
88
+ export function modelCapability(
89
+ model: string,
90
+ capability: string,
91
+ profile: ClaudeCodeProtocolProfile = CLAUDE_CODE_2_1_195_PROFILE,
92
+ ): boolean {
93
+ const id = normalizedOrNull(model);
94
+ if (
95
+ id === null ||
96
+ typeof capability !== "string" ||
97
+ capability.length === 0
98
+ ) {
99
+ return false;
100
+ }
101
+ const entry = profile.supportedModels[id];
102
+ if (entry === undefined) {
103
+ return false;
104
+ }
105
+ return entry.capabilities.includes(capability);
106
+ }
107
+
108
+ /** Whether `model` normalizes to `claude-opus-4-6`. */
109
+ export function isOpus46Model(model: string): boolean {
110
+ return normalizedOrNull(model) === "claude-opus-4-6";
111
+ }
112
+
113
+ /** Whether `model` normalizes to `claude-opus-4-7`. */
114
+ export function isOpus47Model(model: string): boolean {
115
+ return normalizedOrNull(model) === "claude-opus-4-7";
116
+ }
117
+
118
+ /** Whether `model` normalizes to `claude-opus-4-8`. */
119
+ export function isOpus48Model(model: string): boolean {
120
+ return normalizedOrNull(model) === "claude-opus-4-8";
121
+ }
122
+
123
+ /** Whether `model` normalizes to `claude-sonnet-4-6`. */
124
+ export function isSonnet46Model(model: string): boolean {
125
+ return normalizedOrNull(model) === "claude-sonnet-4-6";
126
+ }
127
+
128
+ /** Whether `model` normalizes to `claude-fable-5`. */
129
+ export function isFable5Model(model: string): boolean {
130
+ return normalizedOrNull(model) === "claude-fable-5";
131
+ }
132
+
133
+ /** Whether `model` normalizes to `claude-mythos-5`. */
134
+ export function isMythos5Model(model: string): boolean {
135
+ return normalizedOrNull(model) === "claude-mythos-5";
136
+ }
137
+
138
+ /**
139
+ * Whether `model` belongs to the haiku family.
140
+ *
141
+ * Reuses `modelFamilyOf`, the package's one family classifier, so a new haiku
142
+ * id is classified in exactly one place.
143
+ */
144
+ export function isHaikuModel(model: string): boolean {
145
+ const id = normalizedOrNull(model);
146
+ return id !== null && modelFamilyOf(id) === "haiku";
147
+ }
148
+
149
+ /**
150
+ * Whether `model` is a Claude 3 generation id.
151
+ *
152
+ * The `claude-3-` substring test is the same one the ported capability
153
+ * predicates open with (`model-capabilities.ts`), applied to the normalized
154
+ * id, so a Claude 3 id spelled with a dotted version (`claude-3.5-sonnet`)
155
+ * classifies with its hyphenated spelling.
156
+ */
157
+ export function isClaude3Model(model: string): boolean {
158
+ return normalizedOrNull(model)?.includes("claude-3-") ?? false;
159
+ }
160
+
161
+ /**
162
+ * Whether `model` may receive the 1M-context beta.
163
+ *
164
+ * Three sources, in order: an explicit 1M marker in the id, the active
165
+ * profile's `context.supports1mBeta`, and -- for ids the profile does not
166
+ * catalogue -- the ported family set (`claude-sonnet-4*`, Opus 4.6/4.7/4.8).
167
+ */
168
+ export function isEligibleFor1MContext(
169
+ model: string,
170
+ profile: ClaudeCodeProtocolProfile = CLAUDE_CODE_2_1_195_PROFILE,
171
+ ): boolean {
172
+ const id = normalizedOrNull(model);
173
+ if (id === null) {
174
+ return false;
175
+ }
176
+ if (ONE_MILLION_ELIGIBLE_MARKER_RE.test(model)) {
177
+ return true;
178
+ }
179
+ const entry = profile.supportedModels[id];
180
+ if (entry !== undefined) {
181
+ return entry.context?.supports1mBeta === true;
182
+ }
183
+ return (
184
+ id.startsWith("claude-sonnet-4") ||
185
+ id === "claude-opus-4-6" ||
186
+ id === "claude-opus-4-7" ||
187
+ id === "claude-opus-4-8"
188
+ );
189
+ }
190
+
191
+ /**
192
+ * Whether `model` names 1M context explicitly and therefore always uses it.
193
+ *
194
+ * Marker-only by design. The catalogue's `context.native1m` is a DIFFERENT
195
+ * question -- the model's native window -- and consulting it here would make
196
+ * every natively-1M id answer true, which is not what a static "always send
197
+ * 1M" gate means. Use `modelContextWindow`-shaped catalogue reads for that.
198
+ */
199
+ export function hasOneMillionContext(model: string): boolean {
200
+ return normalizedOrNull(model) !== null && ONE_MILLION_MARKER_RE.test(model);
201
+ }
202
+
203
+ /**
204
+ * Whether `model` supports the structured-outputs beta.
205
+ *
206
+ * Delegates to the predicate ported from the genuine client (`j4e`), which is
207
+ * narrower and better evidenced than a family-shaped heuristic.
208
+ */
209
+ export function supportsStructuredOutputs(model: string): boolean {
210
+ const id = normalizedOrNull(model);
211
+ return id !== null && portedStructuredOutputs(id);
212
+ }
213
+
214
+ /** Whether `model` supports the web-search tool. */
215
+ export function supportsWebSearch(model: string): boolean {
216
+ const id = normalizedOrNull(model);
217
+ return (
218
+ id !== null && WEB_SEARCH_VENDOR_TOKENS.some((token) => id.includes(token))
219
+ );
220
+ }
221
+
222
+ /**
223
+ * Whether `model` uses adaptive thinking (`{type: "adaptive"}`) instead of a
224
+ * manual `budget_tokens`.
225
+ *
226
+ * The union of the named family predicates, not a catalogue read: this gates
227
+ * the shape of the emitted `thinking` block, and an uncatalogued id must not
228
+ * inherit adaptive thinking from the permissive capability fallback. Ask
229
+ * `modelCapability(model, "adaptive_thinking", profile)` for the catalogue's
230
+ * answer.
231
+ */
232
+ export function isAdaptiveThinkingModel(model: string): boolean {
233
+ return (
234
+ isOpus46Model(model) ||
235
+ isOpus47Model(model) ||
236
+ isOpus48Model(model) ||
237
+ isSonnet46Model(model) ||
238
+ isFable5Model(model) ||
239
+ isMythos5Model(model)
240
+ );
241
+ }