@cortexkit/anthropic-auth-core 1.22.0 → 1.23.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 (48) hide show
  1. package/dist/accounts.d.ts +59 -2
  2. package/dist/accounts.js +666 -115
  3. package/dist/auth.js +3 -0
  4. package/dist/cachekeep.d.ts +18 -1
  5. package/dist/cachekeep.js +108 -82
  6. package/dist/cch.d.ts +11 -1
  7. package/dist/cch.js +48 -3
  8. package/dist/claude-code.d.ts +7 -1
  9. package/dist/claude-code.js +18 -3
  10. package/dist/claustrum-enrollment-registry.d.ts +17 -0
  11. package/dist/claustrum-enrollment-registry.js +194 -0
  12. package/dist/claustrum-enrollment.d.ts +70 -0
  13. package/dist/claustrum-enrollment.js +485 -0
  14. package/dist/claustrum-scoped-roster.d.ts +23 -0
  15. package/dist/claustrum-scoped-roster.js +194 -0
  16. package/dist/claustrum-scoped-runtime.d.ts +28 -0
  17. package/dist/claustrum-scoped-runtime.js +230 -0
  18. package/dist/claustrum-scoped.d.ts +39 -0
  19. package/dist/claustrum-scoped.js +193 -0
  20. package/dist/claustrum.d.ts +149 -6
  21. package/dist/claustrum.js +990 -48
  22. package/dist/commands/account.d.ts +40 -12
  23. package/dist/commands/account.js +138 -9
  24. package/dist/constants.d.ts +3 -2
  25. package/dist/constants.js +3 -2
  26. package/dist/custom-headers.d.ts +3 -0
  27. package/dist/custom-headers.js +102 -0
  28. package/dist/fast.js +1 -1
  29. package/dist/index.d.ts +7 -0
  30. package/dist/index.js +7 -0
  31. package/dist/logger.js +3 -1
  32. package/dist/model-remap.d.ts +27 -0
  33. package/dist/model-remap.js +72 -0
  34. package/dist/models.d.ts +19 -0
  35. package/dist/models.js +48 -12
  36. package/dist/oauth-profile.d.ts +2 -1
  37. package/dist/oauth-profile.js +6 -0
  38. package/dist/prime.d.ts +11 -3
  39. package/dist/prime.js +25 -7
  40. package/dist/quota-header-feed.d.ts +13 -0
  41. package/dist/quota-header-feed.js +60 -8
  42. package/dist/quota-manager.d.ts +15 -1
  43. package/dist/quota-manager.js +73 -22
  44. package/dist/relay.d.ts +8 -0
  45. package/dist/relay.js +72 -12
  46. package/dist/sticky-routing.d.ts +2 -0
  47. package/dist/sticky-routing.js +19 -1
  48. package/package.json +3 -2
@@ -1,8 +1,12 @@
1
- import type { AccountStorage } from '../accounts.ts';
2
- import type { ClaustrumDetection } from '../claustrum.ts';
1
+ import type { AccountStorage, ClaustrumMode, FallbackAccount } from '../accounts.ts';
2
+ import type { ClaustrumDetection, CustodyHandleResolution } from '../claustrum.ts';
3
+ import type { ClaustrumEnrollmentStatus } from '../claustrum-enrollment.ts';
3
4
  export declare const CLAUDE_ACCOUNT_COMMAND_NAME = "claude-account";
4
5
  export type AccountCommandAction = {
5
6
  type: 'status';
7
+ } | {
8
+ type: 'claustrum-mode';
9
+ mode: ClaustrumMode;
6
10
  } | {
7
11
  type: 'enable';
8
12
  id: string;
@@ -20,6 +24,8 @@ export type AccountCommandAction = {
20
24
  id: string;
21
25
  } | {
22
26
  type: 'reset-backoff';
27
+ } | {
28
+ type: 'enrollment-reset';
23
29
  } | {
24
30
  type: 'add-apikey';
25
31
  apiKey: string;
@@ -35,6 +41,17 @@ export type AccountCommandAction = {
35
41
  } | {
36
42
  type: 'usage';
37
43
  };
44
+ export type AccountCommandResult = {
45
+ text: string;
46
+ updated?: {
47
+ id: string;
48
+ action: 'enable' | 'disable' | 'remove' | 'reorder' | 'reset-backoff';
49
+ enabled?: boolean;
50
+ previousOrder?: string[];
51
+ newOrder?: string[];
52
+ };
53
+ };
54
+ export type ClaustrumModeTransition = (mode: ClaustrumMode) => Promise<AccountCommandResult>;
38
55
  export declare function parseAccountCommandAction(argumentsText: string): AccountCommandAction;
39
56
  export interface AccountListItem {
40
57
  id: string;
@@ -44,18 +61,29 @@ export interface AccountListItem {
44
61
  quotaPercent: number | null;
45
62
  tierLabel?: string;
46
63
  }
64
+ export type CustodyStatusState = 'na' | 'off' | 'on-vault-served' | 'on-vault-reauth' | 'on-cold' | 'unknown-identity' | 'on-identity-mismatch' | 'on-corrupt-binding';
65
+ export declare function custodyStatusLabel(state: CustodyStatusState): string;
66
+ export declare function formatEnrollmentStatus(status: ClaustrumEnrollmentStatus, scopedServing?: boolean): string[];
67
+ export type AccountCommandStatusProjection = {
68
+ claustrumDetection: string;
69
+ claustrumEnrollment?: ClaustrumEnrollmentStatus;
70
+ custodyMode?: 'local' | 'claustrum';
71
+ custodyModeKnown?: boolean;
72
+ accounts: Array<AccountListItem & {
73
+ claustrumGate: 'on' | 'off' | 'na';
74
+ vaultServed: boolean;
75
+ vaultReauth: boolean;
76
+ custodyState: CustodyStatusState;
77
+ }>;
78
+ };
47
79
  export declare function buildAccountList(storage: AccountStorage): AccountListItem[];
48
80
  export declare function executeAccountCommand(input: {
49
81
  argumentsText: string;
50
82
  storage: AccountStorage;
51
83
  claustrum?: ClaustrumDetection;
52
- }): {
53
- text: string;
54
- updated?: {
55
- id: string;
56
- action: 'enable' | 'disable' | 'remove' | 'reorder' | 'reset-backoff';
57
- enabled?: boolean;
58
- previousOrder?: string[];
59
- newOrder?: string[];
60
- };
61
- };
84
+ statusProjection?: AccountCommandStatusProjection;
85
+ resolveCustodyBinding?: (account: FallbackAccount) => CustodyHandleResolution;
86
+ path?: string;
87
+ transition?: ClaustrumModeTransition;
88
+ resetEnrollment?: () => Promise<AccountCommandResult>;
89
+ }): Promise<AccountCommandResult>;
@@ -1,4 +1,4 @@
1
- import { isClaustrumEnabledForAccount } from '../accounts.js';
1
+ import { getClaustrumMode, isOAuthAccount, isOAuthAccountVaultOwned, } from '../accounts.js';
2
2
  import { formatOAuthAccountTier } from '../oauth-profile.js';
3
3
  export const CLAUDE_ACCOUNT_COMMAND_NAME = 'claude-account';
4
4
  export function parseAccountCommandAction(argumentsText) {
@@ -19,6 +19,14 @@ export function parseAccountCommandAction(argumentsText) {
19
19
  return { type: 'move-down', id: rest };
20
20
  if (action === 'reset-backoff' && !rest)
21
21
  return { type: 'reset-backoff' };
22
+ if (action === 'enrollment-reset' && !rest)
23
+ return { type: 'enrollment-reset' };
24
+ if (action === 'claustrum' && !rest) {
25
+ return { type: 'claustrum-mode', mode: 'claustrum' };
26
+ }
27
+ if (action === 'local' && !rest) {
28
+ return { type: 'claustrum-mode', mode: 'local' };
29
+ }
22
30
  if (action === 'add-apikey' && rest) {
23
31
  let remaining = rest;
24
32
  let baseURL;
@@ -84,6 +92,71 @@ export function parseAccountCommandAction(argumentsText) {
84
92
  }
85
93
  return { type: 'usage' };
86
94
  }
95
+ export function custodyStatusLabel(state) {
96
+ switch (state) {
97
+ case 'na':
98
+ return 'n/a (OpenCode-managed)';
99
+ case 'off':
100
+ return 'not enrolled';
101
+ case 'on-vault-served':
102
+ return 'vault-served';
103
+ case 'on-vault-reauth':
104
+ return 'vault reauth';
105
+ case 'on-cold':
106
+ return 'vault cold';
107
+ case 'unknown-identity':
108
+ return 'unknown identity';
109
+ case 'on-identity-mismatch':
110
+ return 'identity mismatch';
111
+ case 'on-corrupt-binding':
112
+ return 'corrupt binding';
113
+ }
114
+ }
115
+ export function formatEnrollmentStatus(status, scopedServing = false) {
116
+ switch (status.state) {
117
+ case 'idle':
118
+ return ['- Enrollment: not enrolled'];
119
+ case 'busy':
120
+ return ['- Enrollment: another plugin process is reconciling'];
121
+ case 'unavailable':
122
+ return ['- Enrollment: temporarily unavailable'];
123
+ case 'pending':
124
+ return status.requestId && /^[A-Za-z0-9_-]{1,128}$/.test(status.requestId)
125
+ ? [
126
+ `- Enrollment: pending approval (${status.requestId})`,
127
+ `- Approve: \`ck auth enroll approve --request-id ${status.requestId}\``,
128
+ ]
129
+ : status.requestId
130
+ ? [
131
+ '- Enrollment: pending approval; inspect it with `ck auth enroll list`',
132
+ ]
133
+ : ['- Enrollment: preparing request'];
134
+ case 'approved': {
135
+ const name = status.approvedName ?? status.proposedName;
136
+ return /^[A-Za-z0-9_-]{1,128}$/.test(name)
137
+ ? [
138
+ `- Enrollment: approved as enrolled:${name} (generation ${status.tokenGeneration}${scopedServing ? '' : '; scoped serving not active yet'})`,
139
+ ...(scopedServing
140
+ ? []
141
+ : [
142
+ `- Grant: \`ck auth grant --principal enrolled:${name} --selector-kind category --selector anthropic-native --operation read\``,
143
+ ]),
144
+ ]
145
+ : [
146
+ `- Enrollment: approved (generation ${status.tokenGeneration}${scopedServing ? '' : '; scoped serving not active yet'})`,
147
+ ...(scopedServing
148
+ ? []
149
+ : [
150
+ '- Inspect the approved name with `ck auth enroll list` before granting access.',
151
+ ]),
152
+ ];
153
+ }
154
+ case 'denied':
155
+ return ['- Enrollment: denied by operator'];
156
+ case 'blocked':
157
+ return [`- Enrollment: blocked (${status.code})`];
158
+ }
159
+ }
87
160
  export function buildAccountList(storage) {
88
161
  const list = [];
89
162
  const fiveHour = storage.quota?.mainQuota?.five_hour;
@@ -116,29 +189,52 @@ const USAGE_TEXT = [
116
189
  ' /claude-account enable <id> Enable a fallback account',
117
190
  ' /claude-account disable <id> Disable a fallback account',
118
191
  ' /claude-account remove <id> Remove a fallback account',
192
+ ' /claude-account claustrum Enable Claustrum mode',
193
+ ' /claude-account local Enable local mode',
119
194
  ' /claude-account move-up <id> Move a fallback account up',
120
195
  ' /claude-account move-down <id> Move a fallback account down',
121
196
  ' /claude-account reset-backoff Clear main OAuth refresh and quota backoff',
197
+ ' /claude-account enrollment-reset Retry a denied/blocked enrollment',
122
198
  ' /claude-account add-apikey <key> Add an API key fallback account',
123
199
  ' /claude-account add-oauth-start Start OAuth device flow',
124
200
  ' /claude-account add-oauth-finish <code> Complete OAuth flow',
125
201
  ].join('\n');
126
- export function executeAccountCommand(input) {
202
+ export async function executeAccountCommand(input) {
127
203
  const action = parseAccountCommandAction(input.argumentsText);
128
204
  const accounts = input.storage.accounts;
129
205
  const mainId = 'main';
130
206
  if (action.type === 'status') {
131
- const list = buildAccountList(input.storage);
132
- const detection = input.claustrum?.status ?? 'unknown';
133
- const lines = ['## Claude Accounts', '', `- Claustrum: ${detection}`, ''];
207
+ const list = input.statusProjection?.accounts ?? buildAccountList(input.storage);
208
+ const detection = input.statusProjection?.claustrumDetection ??
209
+ input.claustrum?.status ??
210
+ 'unknown';
211
+ const lines = [
212
+ '## Claude Accounts',
213
+ '',
214
+ `- Custody mode: ${getClaustrumMode(input.storage)}`,
215
+ `- Claustrum: ${detection}`,
216
+ ...(input.statusProjection?.claustrumEnrollment
217
+ ? formatEnrollmentStatus(input.statusProjection.claustrumEnrollment)
218
+ : []),
219
+ '',
220
+ ];
134
221
  for (const a of list) {
135
222
  const pct = a.quotaPercent != null ? ` ${Math.round(a.quotaPercent)}%` : '';
136
223
  const status = !a.enabled ? ' (disabled)' : '';
137
224
  const tier = a.tierLabel ? ` · ${a.tierLabel}` : '';
138
- const gate = a.id === mainId
139
- ? ' · gate n/a (OpenCode managed)'
140
- : ` · gate ${isClaustrumEnabledForAccount(input.storage, a.id) ? 'on' : 'off'}`;
141
- lines.push(`- **${a.label}** [${a.role}]${tier}${status}${pct}${gate}`);
225
+ const projected = input.statusProjection?.accounts.find((account) => account.id === a.id);
226
+ const storedAccount = input.storage.accounts.find((account) => account.id === a.id);
227
+ const binding = storedAccount
228
+ ? input.resolveCustodyBinding?.(storedAccount)
229
+ : undefined;
230
+ const custody = projected
231
+ ? custodyStatusLabel(projected.custodyState)
232
+ : a.id !== mainId &&
233
+ storedAccount &&
234
+ isOAuthAccountVaultOwned(input.storage, storedAccount, binding)
235
+ ? custodyStatusLabel('on-cold')
236
+ : 'local';
237
+ lines.push(`- **${a.label}** [${a.role}]${tier}${status}${pct} · ${custody}`);
142
238
  }
143
239
  lines.push('', USAGE_TEXT);
144
240
  return { text: lines.join('\n') };
@@ -146,6 +242,23 @@ export function executeAccountCommand(input) {
146
242
  if (action.type === 'usage') {
147
243
  return { text: USAGE_TEXT };
148
244
  }
245
+ if (action.type === 'claustrum-mode') {
246
+ if (!input.transition) {
247
+ return { text: 'Claustrum mode transition is unavailable.' };
248
+ }
249
+ return input.transition(action.mode);
250
+ }
251
+ if (action.type === 'enrollment-reset') {
252
+ if (getClaustrumMode(input.storage) !== 'claustrum') {
253
+ return {
254
+ text: 'Claustrum enrollment is available only in Claustrum mode.',
255
+ };
256
+ }
257
+ if (!input.resetEnrollment) {
258
+ return { text: 'Claustrum enrollment reset is unavailable.' };
259
+ }
260
+ return input.resetEnrollment();
261
+ }
149
262
  if (action.type === 'add-apikey') {
150
263
  return { text: 'add-apikey' };
151
264
  }
@@ -186,6 +299,22 @@ export function executeAccountCommand(input) {
186
299
  if (!target) {
187
300
  return { text: `Account "${id}" not found.` };
188
301
  }
302
+ if (getClaustrumMode(input.storage) === 'claustrum' &&
303
+ isOAuthAccount(target) &&
304
+ target.claustrumScopedCredentialId) {
305
+ return {
306
+ text: `Account "${target.label ?? id}" is managed by Claustrum. Disable it to stop routing, or remove it from the vault.`,
307
+ };
308
+ }
309
+ const binding = input.resolveCustodyBinding?.(target);
310
+ if (getClaustrumMode(input.storage) === 'claustrum' &&
311
+ isOAuthAccount(target) &&
312
+ binding?.status === 'resolved' &&
313
+ binding.source === 'manifest') {
314
+ return {
315
+ text: `Account "${target.label ?? id}" is bound by the Claustrum manifest. Disable it to stop routing, or remove its Claustrum binding before removing the routing row.`,
316
+ };
317
+ }
189
318
  return {
190
319
  text: `Account "${target.label ?? id}" removed.`,
191
320
  updated: { id, action: 'remove' },
@@ -5,6 +5,7 @@ export declare const AUTHORIZE_URLS: {
5
5
  };
6
6
  export declare const CODE_CALLBACK_URL = "https://platform.claude.com/oauth/code/callback";
7
7
  export declare const TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
8
+ export declare const CUSTODY_HANDLE_PATTERN: RegExp;
8
9
  export declare const AXIOS_USER_AGENT = "axios/1.15.2";
9
10
  export declare const OAUTH_SCOPES: string[];
10
11
  export declare const REFRESH_SCOPE = "user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload";
@@ -18,11 +19,11 @@ export declare const CLAUDE_CODE_IDENTITY = "You are Claude Code, Anthropic's of
18
19
  export declare const PARALLEL_TOOL_CALLS_SYSTEM_PROMPT: string;
19
20
  export declare const CCH_SALT = "59cf53e54c78";
20
21
  export declare const CCH_POSITIONS: number[];
21
- export declare const CLAUDE_CODE_VERSION = "2.1.258";
22
+ export declare const CLAUDE_CODE_VERSION = "2.1.280";
22
23
  export declare const CLAUDE_CODE_ENTRYPOINT = "cli";
23
24
  export declare const CLAUDE_CODE_STAINLESS_PACKAGE_VERSION = "0.112.1";
24
25
  export declare const CLAUDE_CODE_STAINLESS_RUNTIME_VERSION = "v26.3.0";
25
- export declare const USER_AGENT = "claude-cli/2.1.258 (external, cli)";
26
+ export declare const USER_AGENT = "claude-cli/2.1.280 (external, cli)";
26
27
  export declare const CACHE_1H_MODES: readonly ['explicit', 'automatic', 'hybrid'];
27
28
  export type Cache1hMode = (typeof CACHE_1H_MODES)[number];
28
29
  export declare const DEFAULT_CACHE_1H_MODE: Cache1hMode;
package/dist/constants.js CHANGED
@@ -5,6 +5,7 @@ export const AUTHORIZE_URLS = {
5
5
  };
6
6
  export const CODE_CALLBACK_URL = 'https://platform.claude.com/oauth/code/callback';
7
7
  export const TOKEN_URL = 'https://platform.claude.com/v1/oauth/token';
8
+ export const CUSTODY_HANDLE_PATTERN = /^ckh_[A-Za-z0-9_-]{43}$/;
8
9
  export const AXIOS_USER_AGENT = 'axios/1.15.2';
9
10
  export const OAUTH_SCOPES = [
10
11
  'org:create_api_key',
@@ -46,11 +47,11 @@ export const PARALLEL_TOOL_CALLS_SYSTEM_PROMPT = [
46
47
  ].join('\n');
47
48
  export const CCH_SALT = '59cf53e54c78';
48
49
  export const CCH_POSITIONS = [4, 7, 20];
49
- export const CLAUDE_CODE_VERSION = '2.1.258';
50
+ export const CLAUDE_CODE_VERSION = '2.1.280';
50
51
  export const CLAUDE_CODE_ENTRYPOINT = 'cli';
51
52
  export const CLAUDE_CODE_STAINLESS_PACKAGE_VERSION = '0.112.1';
52
53
  export const CLAUDE_CODE_STAINLESS_RUNTIME_VERSION = 'v26.3.0';
53
- export const USER_AGENT = 'claude-cli/2.1.258 (external, cli)';
54
+ export const USER_AGENT = `claude-cli/${CLAUDE_CODE_VERSION} (external, ${CLAUDE_CODE_ENTRYPOINT})`;
54
55
  export const CACHE_1H_MODES = ['explicit', 'automatic', 'hybrid'];
55
56
  export const DEFAULT_CACHE_1H_MODE = 'explicit';
56
57
  /**
@@ -0,0 +1,3 @@
1
+ export declare const ANTHROPIC_CUSTOM_HEADERS_ENV = "ANTHROPIC_CUSTOM_HEADERS";
2
+ export declare function parseCustomHeaders(raw: string | undefined): Headers;
3
+ export declare function applyCustomHeaders(headers: Headers, raw?: string | undefined): Headers;
@@ -0,0 +1,102 @@
1
+ import { parseJsonRedacted } from './json.js';
2
+ import { logger } from './logger.js';
3
+ export const ANTHROPIC_CUSTOM_HEADERS_ENV = 'ANTHROPIC_CUSTOM_HEADERS';
4
+ const CUSTOM_HEADER_CACHE_LIMIT = 64;
5
+ const parsedHeadersByRawValue = new Map();
6
+ const warnedMalformedRawValues = new Set();
7
+ // Proxy-specific metadata is allowed, but custom configuration must never
8
+ // replace route authentication, protocol negotiation, body framing, or
9
+ // plugin-internal correlation headers after those guards have run.
10
+ const PROTECTED_HEADERS = new Set([
11
+ 'authorization',
12
+ 'proxy-authorization',
13
+ 'x-api-key',
14
+ 'host',
15
+ 'content-length',
16
+ 'content-type',
17
+ 'connection',
18
+ 'transfer-encoding',
19
+ 'upgrade',
20
+ 'keep-alive',
21
+ 'te',
22
+ 'trailer',
23
+ 'anthropic-version',
24
+ 'anthropic-beta',
25
+ 'x-parent-session-id',
26
+ 'x-session-affinity',
27
+ 'x-opencode-session',
28
+ 'x-anthropic-effort-plan',
29
+ 'x-cortexkit-billing-lineage',
30
+ ]);
31
+ function cacheParsed(raw, entries) {
32
+ parsedHeadersByRawValue.delete(raw);
33
+ parsedHeadersByRawValue.set(raw, entries);
34
+ while (parsedHeadersByRawValue.size > CUSTOM_HEADER_CACHE_LIMIT) {
35
+ const oldest = parsedHeadersByRawValue.keys().next().value;
36
+ if (oldest === undefined)
37
+ break;
38
+ parsedHeadersByRawValue.delete(oldest);
39
+ warnedMalformedRawValues.delete(oldest);
40
+ }
41
+ }
42
+ function setCustomHeader(headers, name, value) {
43
+ if (PROTECTED_HEADERS.has(name.trim().toLowerCase())) {
44
+ throw new TypeError('protected header');
45
+ }
46
+ headers.set(name, value);
47
+ }
48
+ export function parseCustomHeaders(raw) {
49
+ if (!raw?.trim())
50
+ return new Headers();
51
+ const cached = parsedHeadersByRawValue.get(raw);
52
+ if (cached !== undefined || parsedHeadersByRawValue.has(raw)) {
53
+ return new Headers(cached ?? []);
54
+ }
55
+ try {
56
+ const headers = new Headers();
57
+ const trimmed = raw.trim();
58
+ if (!trimmed.startsWith('{') && !trimmed.startsWith('[')) {
59
+ for (const entry of trimmed
60
+ .split(/\r?\n|,\s*(?=[^,\s:]+:)/)
61
+ .map((value) => value.trim())
62
+ .filter(Boolean)) {
63
+ const separator = entry.indexOf(':');
64
+ if (separator <= 0) {
65
+ throw new TypeError('invalid header entry');
66
+ }
67
+ setCustomHeader(headers, entry.slice(0, separator).trim(), entry.slice(separator + 1).trim());
68
+ }
69
+ }
70
+ else {
71
+ const parsed = parseJsonRedacted(trimmed);
72
+ if (parsed == null ||
73
+ typeof parsed !== 'object' ||
74
+ Array.isArray(parsed)) {
75
+ throw new TypeError('invalid header object');
76
+ }
77
+ for (const [key, value] of Object.entries(parsed)) {
78
+ if (value == null)
79
+ continue;
80
+ setCustomHeader(headers, key, Array.isArray(value) ? value.map(String).join(', ') : String(value));
81
+ }
82
+ }
83
+ const entries = [...headers.entries()];
84
+ cacheParsed(raw, entries);
85
+ return new Headers(entries);
86
+ }
87
+ catch {
88
+ cacheParsed(raw, null);
89
+ if (!warnedMalformedRawValues.has(raw)) {
90
+ warnedMalformedRawValues.add(raw);
91
+ logger.warn('custom-headers', 'ignoring malformed ANTHROPIC_CUSTOM_HEADERS', { reason: 'invalid or protected header configuration' });
92
+ }
93
+ return new Headers();
94
+ }
95
+ }
96
+ export function applyCustomHeaders(headers, raw = process.env[ANTHROPIC_CUSTOM_HEADERS_ENV]) {
97
+ const customHeaders = parseCustomHeaders(raw);
98
+ customHeaders.forEach((value, key) => {
99
+ headers.set(key, value);
100
+ });
101
+ return headers;
102
+ }
package/dist/fast.js CHANGED
@@ -32,7 +32,7 @@ export function buildFastModeStatusSummary(input) {
32
32
  `- Enabled: ${enabled ? 'enabled' : 'disabled'}`,
33
33
  '- Persisted: ~/.config/opencode/anthropic-auth.json',
34
34
  '- Scope: adds Anthropic fast mode to supported Opus requests',
35
- '- Supported models: claude-opus-4-6, claude-opus-4-7, claude-opus-4-8, and claude-opus-5',
35
+ '- Supported models: claude-opus-4-6, claude-opus-4-7, claude-opus-4-8, claude-opus-5, and claude-opus-5-5',
36
36
  '- Request changes: adds `speed: "fast"` and the `fast-mode-2026-02-01` beta header',
37
37
  '- Note: fast and standard speeds do not share prompt-cache prefixes',
38
38
  ].join('\n');
package/dist/index.d.ts CHANGED
@@ -6,8 +6,14 @@ export * from './cachekeep-registry.ts';
6
6
  export * from './cch.ts';
7
7
  export * from './claude-code.ts';
8
8
  export * from './claustrum.ts';
9
+ export * from './claustrum-enrollment.ts';
10
+ export * from './claustrum-enrollment-registry.ts';
11
+ export * from './claustrum-scoped.ts';
12
+ export * from './claustrum-scoped-roster.ts';
13
+ export * from './claustrum-scoped-runtime.ts';
9
14
  export * from './commands/account.ts';
10
15
  export * from './constants.ts';
16
+ export * from './custom-headers.ts';
11
17
  export * from './dump.ts';
12
18
  export * from './fast.ts';
13
19
  export * from './json.ts';
@@ -15,6 +21,7 @@ export * from './killswitch.ts';
15
21
  export * from './logger.ts';
16
22
  export * from './logging.ts';
17
23
  export * from './mid-conversation-output-config.ts';
24
+ export * from './model-remap.ts';
18
25
  export * from './models.ts';
19
26
  export * from './oauth-profile.ts';
20
27
  export * from './pkce.ts';
package/dist/index.js CHANGED
@@ -6,8 +6,14 @@ export * from './cachekeep-registry.js';
6
6
  export * from './cch.js';
7
7
  export * from './claude-code.js';
8
8
  export * from './claustrum.js';
9
+ export * from './claustrum-enrollment.js';
10
+ export * from './claustrum-enrollment-registry.js';
11
+ export * from './claustrum-scoped.js';
12
+ export * from './claustrum-scoped-roster.js';
13
+ export * from './claustrum-scoped-runtime.js';
9
14
  export * from './commands/account.js';
10
15
  export * from './constants.js';
16
+ export * from './custom-headers.js';
11
17
  export * from './dump.js';
12
18
  export * from './fast.js';
13
19
  export * from './json.js';
@@ -15,6 +21,7 @@ export * from './killswitch.js';
15
21
  export * from './logger.js';
16
22
  export * from './logging.js';
17
23
  export * from './mid-conversation-output-config.js';
24
+ export * from './model-remap.js';
18
25
  export * from './models.js';
19
26
  export * from './oauth-profile.js';
20
27
  export * from './pkce.js';
package/dist/logger.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as os from 'node:os';
3
3
  import * as path from 'node:path';
4
+ import { CUSTODY_HANDLE_PATTERN } from './constants.js';
4
5
  const LEVEL_ORDER = {
5
6
  error: 0,
6
7
  warn: 1,
@@ -75,7 +76,7 @@ function rotateLogFile() {
75
76
  }
76
77
  }
77
78
  // -- Redaction ------------------------------------------------------------
78
- export const SECRET_KEY_EXACT = /^(authorization|x-api-key|cookie|set-cookie|refresh|access|token)$/i;
79
+ export const SECRET_KEY_EXACT = /^(authorization|x-api-key|cookie|set-cookie|refresh|access|token|handle)$/i;
79
80
  export function isSecretKey(key) {
80
81
  if (SECRET_KEY_EXACT.test(key))
81
82
  return true;
@@ -92,6 +93,7 @@ const REDACT_VALUE_PATTERNS = [
92
93
  /Bearer\s+\S+/,
93
94
  /sk-[A-Za-z0-9-]+/,
94
95
  /eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/,
96
+ CUSTODY_HANDLE_PATTERN,
95
97
  ];
96
98
  function isRedactableValue(value) {
97
99
  if (typeof value !== 'string')
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Remap canonical Claude model IDs to proxy-compatible names
3
+ * using ANTHROPIC_DEFAULT_*_MODEL env vars.
4
+ *
5
+ * LiteLLM/proxy backends often use shorter model aliases
6
+ * (e.g. `claude-sonnet-4-6` instead of `claude-sonnet-4-20250514`).
7
+ * These proxy-route variables follow the Claude Code convention, except
8
+ * ANTHROPIC_DEFAULT_FABLE_MODEL, which is plugin-specific:
9
+ *
10
+ * ANTHROPIC_MODEL — default for any claude-* model
11
+ * ANTHROPIC_DEFAULT_SONNET_MODEL — models matching claude-sonnet-*
12
+ * ANTHROPIC_DEFAULT_OPUS_MODEL — models matching claude-opus-*
13
+ * ANTHROPIC_DEFAULT_HAIKU_MODEL — models matching claude-haiku-*
14
+ * ANTHROPIC_DEFAULT_FABLE_MODEL — models matching claude-fable / claude-mythos
15
+ *
16
+ * Tier-specific vars take precedence over the generic ANTHROPIC_MODEL.
17
+ */
18
+ /**
19
+ * Resolve a canonical model ID to its proxy-compatible alias.
20
+ * Returns the original model when no env override is configured.
21
+ */
22
+ export declare function remapModelId(model: string): string;
23
+ /**
24
+ * Remap the `model` field in a parsed request body in place.
25
+ * Returns true if the model was changed.
26
+ */
27
+ export declare function remapRequestBodyModel(parsed: Record<string, unknown>): boolean;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Remap canonical Claude model IDs to proxy-compatible names
3
+ * using ANTHROPIC_DEFAULT_*_MODEL env vars.
4
+ *
5
+ * LiteLLM/proxy backends often use shorter model aliases
6
+ * (e.g. `claude-sonnet-4-6` instead of `claude-sonnet-4-20250514`).
7
+ * These proxy-route variables follow the Claude Code convention, except
8
+ * ANTHROPIC_DEFAULT_FABLE_MODEL, which is plugin-specific:
9
+ *
10
+ * ANTHROPIC_MODEL — default for any claude-* model
11
+ * ANTHROPIC_DEFAULT_SONNET_MODEL — models matching claude-sonnet-*
12
+ * ANTHROPIC_DEFAULT_OPUS_MODEL — models matching claude-opus-*
13
+ * ANTHROPIC_DEFAULT_HAIKU_MODEL — models matching claude-haiku-*
14
+ * ANTHROPIC_DEFAULT_FABLE_MODEL — models matching claude-fable / claude-mythos
15
+ *
16
+ * Tier-specific vars take precedence over the generic ANTHROPIC_MODEL.
17
+ */
18
+ function getEnv(name) {
19
+ const value = process.env[name]?.trim();
20
+ return value || undefined;
21
+ }
22
+ function getModelTier(model) {
23
+ if (/^claude-sonnet(?:-|$)/.test(model))
24
+ return 'sonnet';
25
+ if (/^claude-opus(?:-|$)/.test(model))
26
+ return 'opus';
27
+ if (/^claude-haiku(?:-|$)/.test(model))
28
+ return 'haiku';
29
+ if (/^claude-(?:fable|mythos)(?:-|$)/.test(model))
30
+ return 'fable';
31
+ return null;
32
+ }
33
+ const TIER_ENV_MAP = {
34
+ sonnet: 'ANTHROPIC_DEFAULT_SONNET_MODEL',
35
+ opus: 'ANTHROPIC_DEFAULT_OPUS_MODEL',
36
+ haiku: 'ANTHROPIC_DEFAULT_HAIKU_MODEL',
37
+ fable: 'ANTHROPIC_DEFAULT_FABLE_MODEL',
38
+ };
39
+ /**
40
+ * Resolve a canonical model ID to its proxy-compatible alias.
41
+ * Returns the original model when no env override is configured.
42
+ */
43
+ export function remapModelId(model) {
44
+ if (typeof model !== 'string' || !model)
45
+ return model;
46
+ const tier = getModelTier(model);
47
+ if (tier) {
48
+ const tierModel = getEnv(TIER_ENV_MAP[tier]);
49
+ if (tierModel)
50
+ return tierModel;
51
+ }
52
+ // Generic fallback for any claude-* model
53
+ if (model.startsWith('claude-')) {
54
+ const defaultModel = getEnv('ANTHROPIC_MODEL');
55
+ if (defaultModel)
56
+ return defaultModel;
57
+ }
58
+ return model;
59
+ }
60
+ /**
61
+ * Remap the `model` field in a parsed request body in place.
62
+ * Returns true if the model was changed.
63
+ */
64
+ export function remapRequestBodyModel(parsed) {
65
+ if (typeof parsed.model !== 'string')
66
+ return false;
67
+ const remapped = remapModelId(parsed.model);
68
+ if (remapped === parsed.model)
69
+ return false;
70
+ parsed.model = remapped;
71
+ return true;
72
+ }
package/dist/models.d.ts CHANGED
@@ -9,6 +9,8 @@ export declare const CLAUDE_MYTHOS_5_1_MODEL_ID = "claude-mythos-5-1";
9
9
  * above so per-million-token cost estimation does not conflate families.
10
10
  */
11
11
  export declare const CLAUDE_HAIKU_4_5_MODEL_ID = "claude-haiku-4-5";
12
+ /** Remove OpenCode's context-window qualifier before model-family checks. */
13
+ export declare function normalizeAnthropicModelId(model: string): string;
12
14
  /**
13
15
  * Per-million-token USD pricing for Haiku 4.5. Used to project the cumulative
14
16
  * cost of prime requests from persisted usage counters — never persisted on
@@ -90,4 +92,21 @@ export declare const CLAUDE_OPUS_5_ADAPTIVE_THINKING: {
90
92
  readonly display: 'summarized';
91
93
  };
92
94
  export declare function isClaudeOpus5Model(model: unknown): boolean;
95
+ export declare const CLAUDE_OPUS_5_5_MODEL_ID = "claude-opus-5-5";
96
+ export declare const CLAUDE_OPUS_5_5_RELEASE_DATE = "2026-09-18";
97
+ export declare const CLAUDE_OPUS_5_5_CONTEXT_WINDOW = 1000000;
98
+ export declare const CLAUDE_OPUS_5_5_MAX_OUTPUT_TOKENS = 128000;
99
+ export declare const CLAUDE_OPUS_5_5_PRICING: {
100
+ readonly input: 4;
101
+ readonly output: 20;
102
+ readonly cacheRead: 0.2;
103
+ readonly cacheWrite5m: 5;
104
+ readonly cacheWrite1h: 8;
105
+ };
106
+ export declare const CLAUDE_OPUS_5_5_ADAPTIVE_THINKING: {
107
+ readonly type: 'adaptive';
108
+ readonly display: 'summarized';
109
+ };
110
+ export declare function isClaudeOpus55Model(model: unknown): boolean;
111
+ export declare function isClaudeOpus5FamilyModel(model: unknown): boolean;
93
112
  export declare function isOpenAIReasoningSignature(value: unknown): boolean;