@vacbo/opencode-anthropic-fix 0.1.7 → 0.1.9

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 (107) hide show
  1. package/README.md +88 -88
  2. package/dist/opencode-anthropic-auth-cli.mjs +804 -507
  3. package/dist/opencode-anthropic-auth-plugin.js +4751 -4109
  4. package/package.json +67 -59
  5. package/src/__tests__/billing-edge-cases.test.ts +59 -59
  6. package/src/__tests__/bun-proxy.parallel.test.ts +388 -382
  7. package/src/__tests__/cc-comparison.test.ts +87 -87
  8. package/src/__tests__/cc-credentials.test.ts +254 -250
  9. package/src/__tests__/cch-drift-checker.test.ts +51 -51
  10. package/src/__tests__/cch-native-style.test.ts +56 -56
  11. package/src/__tests__/debug-gating.test.ts +42 -42
  12. package/src/__tests__/decomposition-smoke.test.ts +68 -68
  13. package/src/__tests__/fingerprint-regression.test.ts +575 -566
  14. package/src/__tests__/helpers/conversation-history.smoke.test.ts +271 -271
  15. package/src/__tests__/helpers/conversation-history.ts +119 -119
  16. package/src/__tests__/helpers/deferred.smoke.test.ts +103 -103
  17. package/src/__tests__/helpers/deferred.ts +69 -69
  18. package/src/__tests__/helpers/in-memory-storage.smoke.test.ts +155 -155
  19. package/src/__tests__/helpers/in-memory-storage.ts +88 -88
  20. package/src/__tests__/helpers/mock-bun-proxy.smoke.test.ts +68 -68
  21. package/src/__tests__/helpers/mock-bun-proxy.ts +189 -189
  22. package/src/__tests__/helpers/plugin-fetch-harness.smoke.test.ts +273 -273
  23. package/src/__tests__/helpers/plugin-fetch-harness.ts +288 -288
  24. package/src/__tests__/helpers/sse.smoke.test.ts +236 -236
  25. package/src/__tests__/helpers/sse.ts +209 -209
  26. package/src/__tests__/index.parallel.test.ts +605 -595
  27. package/src/__tests__/sanitization-regex.test.ts +112 -112
  28. package/src/__tests__/state-bounds.test.ts +90 -90
  29. package/src/account-identity.test.ts +197 -192
  30. package/src/account-identity.ts +69 -67
  31. package/src/account-state.test.ts +86 -86
  32. package/src/account-state.ts +25 -25
  33. package/src/accounts/matching.test.ts +335 -0
  34. package/src/accounts/matching.ts +167 -0
  35. package/src/accounts/persistence.test.ts +345 -0
  36. package/src/accounts/persistence.ts +432 -0
  37. package/src/accounts/repair.test.ts +276 -0
  38. package/src/accounts/repair.ts +407 -0
  39. package/src/accounts.dedup.test.ts +621 -621
  40. package/src/accounts.test.ts +933 -929
  41. package/src/accounts.ts +633 -989
  42. package/src/backoff.test.ts +345 -345
  43. package/src/backoff.ts +219 -219
  44. package/src/betas.ts +124 -124
  45. package/src/bun-fetch.test.ts +345 -342
  46. package/src/bun-fetch.ts +424 -424
  47. package/src/bun-proxy.test.ts +25 -25
  48. package/src/bun-proxy.ts +209 -209
  49. package/src/cc-credentials.ts +111 -111
  50. package/src/circuit-breaker.test.ts +184 -184
  51. package/src/circuit-breaker.ts +169 -169
  52. package/src/cli/commands/auth.ts +963 -0
  53. package/src/cli/commands/config.ts +547 -0
  54. package/src/cli/formatting.test.ts +406 -0
  55. package/src/cli/formatting.ts +219 -0
  56. package/src/cli.ts +255 -2022
  57. package/src/commands/handlers/betas.ts +100 -0
  58. package/src/commands/handlers/config.ts +99 -0
  59. package/src/commands/handlers/files.ts +375 -0
  60. package/src/commands/oauth-flow.ts +181 -166
  61. package/src/commands/prompts.ts +61 -61
  62. package/src/commands/router.test.ts +421 -0
  63. package/src/commands/router.ts +143 -635
  64. package/src/config.test.ts +482 -482
  65. package/src/config.ts +412 -404
  66. package/src/constants.ts +48 -48
  67. package/src/drift/cch-constants.ts +95 -95
  68. package/src/env.ts +111 -105
  69. package/src/headers/billing.ts +33 -33
  70. package/src/headers/builder.ts +130 -130
  71. package/src/headers/cch.ts +75 -75
  72. package/src/headers/stainless.ts +25 -25
  73. package/src/headers/user-agent.ts +23 -23
  74. package/src/index.ts +436 -828
  75. package/src/models.ts +27 -27
  76. package/src/oauth.test.ts +102 -102
  77. package/src/oauth.ts +178 -178
  78. package/src/parent-pid-watcher.test.ts +148 -148
  79. package/src/parent-pid-watcher.ts +69 -69
  80. package/src/plugin-helpers.ts +82 -82
  81. package/src/refresh-helpers.ts +145 -139
  82. package/src/refresh-lock.test.ts +94 -94
  83. package/src/refresh-lock.ts +93 -93
  84. package/src/request/body.history.test.ts +579 -571
  85. package/src/request/body.ts +255 -255
  86. package/src/request/metadata.ts +65 -65
  87. package/src/request/retry.test.ts +156 -156
  88. package/src/request/retry.ts +67 -67
  89. package/src/request/url.ts +21 -21
  90. package/src/request-orchestration-helpers.ts +648 -0
  91. package/src/response/index.ts +5 -5
  92. package/src/response/mcp.ts +58 -58
  93. package/src/response/streaming.test.ts +313 -311
  94. package/src/response/streaming.ts +412 -410
  95. package/src/rotation.test.ts +304 -301
  96. package/src/rotation.ts +205 -205
  97. package/src/storage.test.ts +547 -547
  98. package/src/storage.ts +315 -291
  99. package/src/system-prompt/builder.ts +38 -38
  100. package/src/system-prompt/index.ts +5 -5
  101. package/src/system-prompt/normalize.ts +60 -60
  102. package/src/system-prompt/sanitize.ts +30 -30
  103. package/src/thinking.ts +21 -20
  104. package/src/token-refresh.test.ts +265 -265
  105. package/src/token-refresh.ts +219 -214
  106. package/src/types.ts +30 -30
  107. package/dist/bun-proxy.mjs +0 -291
@@ -3,42 +3,42 @@ import { CCH_PLACEHOLDER } from "./cch.js";
3
3
  import { isFalsyEnv } from "../env.js";
4
4
 
5
5
  export function buildAnthropicBillingHeader(claudeCliVersion: string, messages: unknown[]): string {
6
- if (isFalsyEnv(process.env.CLAUDE_CODE_ATTRIBUTION_HEADER)) return "";
6
+ if (isFalsyEnv(process.env.CLAUDE_CODE_ATTRIBUTION_HEADER)) return "";
7
7
 
8
- // CC derives the 3-char cc_version suffix from the first user message using
9
- // SHA-256 with salt "59cf53e54c78" and positions [4,7,20]. The cch field is
10
- // emitted here as the literal placeholder "00000" and replaced later, after
11
- // full-body serialization, by replaceNativeStyleCch() in src/headers/cch.ts.
12
- let versionSuffix = "";
13
- if (Array.isArray(messages)) {
14
- // Find first user message (CC uses first non-meta user turn)
15
- const firstUserMsg = messages.find(
16
- (m) => m !== null && typeof m === "object" && (m as Record<string, unknown>).role === "user",
17
- ) as Record<string, unknown> | undefined;
18
- if (firstUserMsg) {
19
- // Extract text from string or content-block array
20
- let text = "";
21
- const content = firstUserMsg.content;
22
- if (typeof content === "string") {
23
- text = content;
24
- } else if (Array.isArray(content)) {
25
- const textBlock = (content as Array<Record<string, unknown>>).find((b) => b.type === "text");
26
- if (textBlock && typeof textBlock.text === "string") {
27
- text = textBlock.text;
8
+ // CC derives the 3-char cc_version suffix from the first user message using
9
+ // SHA-256 with salt "59cf53e54c78" and positions [4,7,20]. The cch field is
10
+ // emitted here as the literal placeholder "00000" and replaced later, after
11
+ // full-body serialization, by replaceNativeStyleCch() in src/headers/cch.ts.
12
+ let versionSuffix = "";
13
+ if (Array.isArray(messages)) {
14
+ // Find first user message (CC uses first non-meta user turn)
15
+ const firstUserMsg = messages.find(
16
+ (m) => m !== null && typeof m === "object" && (m as Record<string, unknown>).role === "user",
17
+ ) as Record<string, unknown> | undefined;
18
+ if (firstUserMsg) {
19
+ // Extract text from string or content-block array
20
+ let text = "";
21
+ const content = firstUserMsg.content;
22
+ if (typeof content === "string") {
23
+ text = content;
24
+ } else if (Array.isArray(content)) {
25
+ const textBlock = (content as Array<Record<string, unknown>>).find((b) => b.type === "text");
26
+ if (textBlock && typeof textBlock.text === "string") {
27
+ text = textBlock.text;
28
+ }
29
+ }
30
+ if (text) {
31
+ const salt = "59cf53e54c78";
32
+ const picked = [4, 7, 20].map((i) => (i < text.length ? text[i] : "0")).join("");
33
+ const hash = createHash("sha256")
34
+ .update(salt + picked + claudeCliVersion)
35
+ .digest("hex");
36
+ versionSuffix = `.${hash.slice(0, 3)}`;
37
+ }
28
38
  }
29
- }
30
- if (text) {
31
- const salt = "59cf53e54c78";
32
- const picked = [4, 7, 20].map((i) => (i < text.length ? text[i] : "0")).join("");
33
- const hash = createHash("sha256")
34
- .update(salt + picked + claudeCliVersion)
35
- .digest("hex");
36
- versionSuffix = `.${hash.slice(0, 3)}`;
37
- }
38
39
  }
39
- }
40
40
 
41
- const entrypoint = process.env.CLAUDE_CODE_ENTRYPOINT ?? "cli";
41
+ const entrypoint = process.env.CLAUDE_CODE_ENTRYPOINT ?? "cli";
42
42
 
43
- return `x-anthropic-billing-header: cc_version=${claudeCliVersion}${versionSuffix}; cc_entrypoint=${entrypoint}; cch=${CCH_PLACEHOLDER};`;
43
+ return `x-anthropic-billing-header: cc_version=${claudeCliVersion}${versionSuffix}; cc_entrypoint=${entrypoint}; cch=${CCH_PLACEHOLDER};`;
44
44
  }
@@ -6,159 +6,159 @@ import { buildStainlessHelperHeader, getStainlessArch, getStainlessOs } from "./
6
6
  import { buildUserAgent } from "./user-agent.js";
7
7
 
8
8
  function parseAnthropicCustomHeaders(): Record<string, string> {
9
- const raw = process.env.ANTHROPIC_CUSTOM_HEADERS;
10
- if (!raw) return {};
9
+ const raw = process.env.ANTHROPIC_CUSTOM_HEADERS;
10
+ if (!raw) return {};
11
11
 
12
- const headers: Record<string, string> = {};
13
- for (const line of raw.split(/\r?\n/)) {
14
- const trimmed = line.trim();
15
- if (!trimmed) continue;
16
- const sep = trimmed.indexOf(":");
17
- if (sep <= 0) continue;
18
- const key = trimmed.slice(0, sep).trim();
19
- const value = trimmed.slice(sep + 1).trim();
20
- if (!key || !value) continue;
21
- headers[key] = value;
22
- }
12
+ const headers: Record<string, string> = {};
13
+ for (const line of raw.split(/\r?\n/)) {
14
+ const trimmed = line.trim();
15
+ if (!trimmed) continue;
16
+ const sep = trimmed.indexOf(":");
17
+ if (sep <= 0) continue;
18
+ const key = trimmed.slice(0, sep).trim();
19
+ const value = trimmed.slice(sep + 1).trim();
20
+ if (!key || !value) continue;
21
+ headers[key] = value;
22
+ }
23
23
 
24
- return headers;
24
+ return headers;
25
25
  }
26
26
 
27
27
  function detectProvider(requestUrl: URL | null) {
28
- if (!requestUrl) return "anthropic" as const;
29
- const host = requestUrl.hostname.toLowerCase();
30
- if (host.includes("bedrock") || host.includes("amazonaws.com")) return "bedrock" as const;
31
- if (host.includes("aiplatform") || host.includes("vertex")) return "vertex" as const;
32
- if (host.includes("foundry") || host.includes("azure")) return "foundry" as const;
33
- return "anthropic" as const;
28
+ if (!requestUrl) return "anthropic" as const;
29
+ const host = requestUrl.hostname.toLowerCase();
30
+ if (host.includes("bedrock") || host.includes("amazonaws.com")) return "bedrock" as const;
31
+ if (host.includes("aiplatform") || host.includes("vertex")) return "vertex" as const;
32
+ if (host.includes("foundry") || host.includes("azure")) return "foundry" as const;
33
+ return "anthropic" as const;
34
34
  }
35
35
 
36
36
  function parseRequestBodyMetadata(body: string | undefined): {
37
- model: string;
38
- tools: unknown[];
39
- messages: unknown[];
40
- hasFileReferences: boolean;
37
+ model: string;
38
+ tools: unknown[];
39
+ messages: unknown[];
40
+ hasFileReferences: boolean;
41
41
  } {
42
- if (!body || typeof body !== "string") {
43
- return { model: "", tools: [], messages: [], hasFileReferences: false };
44
- }
42
+ if (!body || typeof body !== "string") {
43
+ return { model: "", tools: [], messages: [], hasFileReferences: false };
44
+ }
45
45
 
46
- try {
47
- const parsed = JSON.parse(body) as Record<string, unknown>;
48
- const model = typeof parsed?.model === "string" ? parsed.model : "";
49
- const tools = Array.isArray(parsed?.tools) ? parsed.tools : [];
50
- const messages = Array.isArray(parsed?.messages) ? parsed.messages : [];
51
- // hasFileReferences: check if any message content references files
52
- const hasFileReferences = hasFileIds(parsed);
53
- return { model, tools, messages, hasFileReferences };
54
- } catch {
55
- return { model: "", tools: [], messages: [], hasFileReferences: false };
56
- }
46
+ try {
47
+ const parsed = JSON.parse(body) as Record<string, unknown>;
48
+ const model = typeof parsed?.model === "string" ? parsed.model : "";
49
+ const tools = Array.isArray(parsed?.tools) ? parsed.tools : [];
50
+ const messages = Array.isArray(parsed?.messages) ? parsed.messages : [];
51
+ // hasFileReferences: check if any message content references files
52
+ const hasFileReferences = hasFileIds(parsed);
53
+ return { model, tools, messages, hasFileReferences };
54
+ } catch {
55
+ return { model: "", tools: [], messages: [], hasFileReferences: false };
56
+ }
57
57
  }
58
58
 
59
59
  function hasFileIds(parsed: Record<string, unknown>): boolean {
60
- const str = JSON.stringify(parsed);
61
- return /file[-_][a-zA-Z0-9]{2,}/.test(str);
60
+ const str = JSON.stringify(parsed);
61
+ return /file[-_][a-zA-Z0-9]{2,}/.test(str);
62
62
  }
63
63
 
64
64
  export function buildRequestHeaders(
65
- input: Request | string | URL,
66
- requestInit: Record<string, unknown>,
67
- accessToken: string,
68
- requestBody: string | undefined,
69
- requestUrl: URL | null,
70
- signature: SignatureConfig,
65
+ input: Request | string | URL,
66
+ requestInit: Record<string, unknown>,
67
+ accessToken: string,
68
+ requestBody: string | undefined,
69
+ requestUrl: URL | null,
70
+ signature: SignatureConfig,
71
71
  ): Headers {
72
- const requestHeaders = new Headers();
73
- if (input instanceof Request) {
74
- input.headers.forEach((value, key) => {
75
- requestHeaders.set(key, value);
76
- });
77
- }
78
- const initHeaders = requestInit.headers;
79
- if (initHeaders) {
80
- if (initHeaders instanceof Headers) {
81
- initHeaders.forEach((value, key) => {
82
- requestHeaders.set(key, value);
83
- });
84
- } else if (Array.isArray(initHeaders)) {
85
- for (const [key, value] of initHeaders as [string, string | undefined][]) {
86
- if (typeof value !== "undefined") {
87
- requestHeaders.set(key, String(value));
88
- }
89
- }
90
- } else {
91
- for (const [key, value] of Object.entries(initHeaders as Record<string, unknown>)) {
92
- if (typeof value !== "undefined") {
93
- requestHeaders.set(key, String(value));
72
+ const requestHeaders = new Headers();
73
+ if (input instanceof Request) {
74
+ input.headers.forEach((value, key) => {
75
+ requestHeaders.set(key, value);
76
+ });
77
+ }
78
+ const initHeaders = requestInit.headers;
79
+ if (initHeaders) {
80
+ if (initHeaders instanceof Headers) {
81
+ initHeaders.forEach((value, key) => {
82
+ requestHeaders.set(key, value);
83
+ });
84
+ } else if (Array.isArray(initHeaders)) {
85
+ for (const [key, value] of initHeaders as [string, string | undefined][]) {
86
+ if (typeof value !== "undefined") {
87
+ requestHeaders.set(key, String(value));
88
+ }
89
+ }
90
+ } else {
91
+ for (const [key, value] of Object.entries(initHeaders as Record<string, unknown>)) {
92
+ if (typeof value !== "undefined") {
93
+ requestHeaders.set(key, String(value));
94
+ }
95
+ }
94
96
  }
95
- }
96
97
  }
97
- }
98
98
 
99
- // Preserve all incoming beta headers while ensuring OAuth requirements
100
- const incomingBeta = requestHeaders.get("anthropic-beta") || "";
101
- const { model, tools, messages, hasFileReferences } = parseRequestBodyMetadata(requestBody);
102
- const provider = detectProvider(requestUrl);
103
- const mergedBetas = buildAnthropicBetaHeader(
104
- incomingBeta,
105
- signature.enabled,
106
- model,
107
- provider,
108
- signature.customBetas,
109
- signature.strategy,
110
- requestUrl?.pathname,
111
- hasFileReferences,
112
- );
99
+ // Preserve all incoming beta headers while ensuring OAuth requirements
100
+ const incomingBeta = requestHeaders.get("anthropic-beta") || "";
101
+ const { model, tools, messages, hasFileReferences } = parseRequestBodyMetadata(requestBody);
102
+ const provider = detectProvider(requestUrl);
103
+ const mergedBetas = buildAnthropicBetaHeader(
104
+ incomingBeta,
105
+ signature.enabled,
106
+ model,
107
+ provider,
108
+ signature.customBetas,
109
+ signature.strategy,
110
+ requestUrl?.pathname,
111
+ hasFileReferences,
112
+ );
113
113
 
114
- const authTokenOverride = process.env.ANTHROPIC_AUTH_TOKEN?.trim();
115
- const bearerToken = authTokenOverride || accessToken;
114
+ const authTokenOverride = process.env.ANTHROPIC_AUTH_TOKEN?.trim();
115
+ const bearerToken = authTokenOverride || accessToken;
116
116
 
117
- requestHeaders.set("authorization", `Bearer ${bearerToken}`);
118
- requestHeaders.set("anthropic-beta", mergedBetas);
119
- requestHeaders.set("user-agent", buildUserAgent(signature.claudeCliVersion));
120
- if (signature.enabled) {
121
- requestHeaders.set("anthropic-version", "2023-06-01");
122
- requestHeaders.set("anthropic-dangerous-direct-browser-access", "true");
123
- requestHeaders.set("x-app", "cli");
124
- requestHeaders.set("x-stainless-arch", getStainlessArch(process.arch));
125
- requestHeaders.set("x-stainless-lang", "js");
126
- requestHeaders.set("x-stainless-os", getStainlessOs(process.platform));
127
- // CC's Stainless SDK reports its own package version (0.81.0), not the CLI version
128
- requestHeaders.set("x-stainless-package-version", "0.81.0");
129
- requestHeaders.set("x-stainless-runtime", "node");
130
- requestHeaders.set("x-stainless-runtime-version", process.version);
131
- // CC's SDK default timeout is 600s (600000ms)
132
- requestHeaders.set("x-stainless-timeout", "600");
133
- const incomingRetryCount = requestHeaders.get("x-stainless-retry-count");
134
- requestHeaders.set(
135
- "x-stainless-retry-count",
136
- incomingRetryCount && !isFalsyEnv(incomingRetryCount) ? incomingRetryCount : "0",
137
- );
138
- const stainlessHelpers = buildStainlessHelperHeader(tools, messages);
139
- if (stainlessHelpers) {
140
- requestHeaders.set("x-stainless-helper", stainlessHelpers);
141
- }
117
+ requestHeaders.set("authorization", `Bearer ${bearerToken}`);
118
+ requestHeaders.set("anthropic-beta", mergedBetas);
119
+ requestHeaders.set("user-agent", buildUserAgent(signature.claudeCliVersion));
120
+ if (signature.enabled) {
121
+ requestHeaders.set("anthropic-version", "2023-06-01");
122
+ requestHeaders.set("anthropic-dangerous-direct-browser-access", "true");
123
+ requestHeaders.set("x-app", "cli");
124
+ requestHeaders.set("x-stainless-arch", getStainlessArch(process.arch));
125
+ requestHeaders.set("x-stainless-lang", "js");
126
+ requestHeaders.set("x-stainless-os", getStainlessOs(process.platform));
127
+ // CC's Stainless SDK reports its own package version (0.81.0), not the CLI version
128
+ requestHeaders.set("x-stainless-package-version", "0.81.0");
129
+ requestHeaders.set("x-stainless-runtime", "node");
130
+ requestHeaders.set("x-stainless-runtime-version", process.version);
131
+ // CC's SDK default timeout is 600s (600000ms)
132
+ requestHeaders.set("x-stainless-timeout", "600");
133
+ const incomingRetryCount = requestHeaders.get("x-stainless-retry-count");
134
+ requestHeaders.set(
135
+ "x-stainless-retry-count",
136
+ incomingRetryCount && !isFalsyEnv(incomingRetryCount) ? incomingRetryCount : "0",
137
+ );
138
+ const stainlessHelpers = buildStainlessHelperHeader(tools, messages);
139
+ if (stainlessHelpers) {
140
+ requestHeaders.set("x-stainless-helper", stainlessHelpers);
141
+ }
142
142
 
143
- for (const [key, value] of Object.entries(parseAnthropicCustomHeaders())) {
144
- requestHeaders.set(key, value);
145
- }
146
- if (process.env.CLAUDE_CODE_CONTAINER_ID) {
147
- requestHeaders.set("x-claude-remote-container-id", process.env.CLAUDE_CODE_CONTAINER_ID);
148
- }
149
- if (process.env.CLAUDE_CODE_REMOTE_SESSION_ID) {
150
- requestHeaders.set("x-claude-remote-session-id", process.env.CLAUDE_CODE_REMOTE_SESSION_ID);
151
- }
152
- if (process.env.CLAUDE_AGENT_SDK_CLIENT_APP) {
153
- requestHeaders.set("x-client-app", process.env.CLAUDE_AGENT_SDK_CLIENT_APP);
154
- }
155
- if (isTruthyEnv(process.env.CLAUDE_CODE_ADDITIONAL_PROTECTION)) {
156
- requestHeaders.set("x-anthropic-additional-protection", "true");
143
+ for (const [key, value] of Object.entries(parseAnthropicCustomHeaders())) {
144
+ requestHeaders.set(key, value);
145
+ }
146
+ if (process.env.CLAUDE_CODE_CONTAINER_ID) {
147
+ requestHeaders.set("x-claude-remote-container-id", process.env.CLAUDE_CODE_CONTAINER_ID);
148
+ }
149
+ if (process.env.CLAUDE_CODE_REMOTE_SESSION_ID) {
150
+ requestHeaders.set("x-claude-remote-session-id", process.env.CLAUDE_CODE_REMOTE_SESSION_ID);
151
+ }
152
+ if (process.env.CLAUDE_AGENT_SDK_CLIENT_APP) {
153
+ requestHeaders.set("x-client-app", process.env.CLAUDE_AGENT_SDK_CLIENT_APP);
154
+ }
155
+ if (isTruthyEnv(process.env.CLAUDE_CODE_ADDITIONAL_PROTECTION)) {
156
+ requestHeaders.set("x-anthropic-additional-protection", "true");
157
+ }
158
+ // CC 2.1.98 sends a per-request UUID
159
+ requestHeaders.set("x-client-request-id", randomUUID());
157
160
  }
158
- // CC 2.1.98 sends a per-request UUID
159
- requestHeaders.set("x-client-request-id", randomUUID());
160
- }
161
- requestHeaders.delete("x-api-key");
161
+ requestHeaders.delete("x-api-key");
162
162
 
163
- return requestHeaders;
163
+ return requestHeaders;
164
164
  }
@@ -13,108 +13,108 @@ export const CCH_SEED = 0x6e52_736a_c806_831en;
13
13
  const encoder = new TextEncoder();
14
14
 
15
15
  function toUint64(value: bigint): bigint {
16
- return value & MASK64;
16
+ return value & MASK64;
17
17
  }
18
18
 
19
19
  function rotateLeft64(value: bigint, bits: number): bigint {
20
- const shift = BigInt(bits);
21
- return toUint64((value << shift) | (value >> (64n - shift)));
20
+ const shift = BigInt(bits);
21
+ return toUint64((value << shift) | (value >> (64n - shift)));
22
22
  }
23
23
 
24
24
  function readUint32LE(view: DataView, offset: number): bigint {
25
- return BigInt(view.getUint32(offset, true));
25
+ return BigInt(view.getUint32(offset, true));
26
26
  }
27
27
 
28
28
  function readUint64LE(view: DataView, offset: number): bigint {
29
- return view.getBigUint64(offset, true);
29
+ return view.getBigUint64(offset, true);
30
30
  }
31
31
 
32
32
  function round64(acc: bigint, input: bigint): bigint {
33
- const mixed = toUint64(acc + toUint64(input * PRIME2));
34
- return toUint64(rotateLeft64(mixed, 31) * PRIME1);
33
+ const mixed = toUint64(acc + toUint64(input * PRIME2));
34
+ return toUint64(rotateLeft64(mixed, 31) * PRIME1);
35
35
  }
36
36
 
37
37
  function mergeRound64(acc: bigint, value: bigint): bigint {
38
- const mixed = acc ^ round64(0n, value);
39
- return toUint64(toUint64(mixed) * PRIME1 + PRIME4);
38
+ const mixed = acc ^ round64(0n, value);
39
+ return toUint64(toUint64(mixed) * PRIME1 + PRIME4);
40
40
  }
41
41
 
42
42
  function avalanche64(hash: bigint): bigint {
43
- let mixed = hash ^ (hash >> 33n);
44
- mixed = toUint64(mixed * PRIME2);
45
- mixed ^= mixed >> 29n;
46
- mixed = toUint64(mixed * PRIME3);
47
- mixed ^= mixed >> 32n;
48
- return toUint64(mixed);
43
+ let mixed = hash ^ (hash >> 33n);
44
+ mixed = toUint64(mixed * PRIME2);
45
+ mixed ^= mixed >> 29n;
46
+ mixed = toUint64(mixed * PRIME3);
47
+ mixed ^= mixed >> 32n;
48
+ return toUint64(mixed);
49
49
  }
50
50
 
51
51
  export function xxHash64(input: Uint8Array, seed: bigint = CCH_SEED): bigint {
52
- const view = new DataView(input.buffer, input.byteOffset, input.byteLength);
53
- const length = input.byteLength;
54
- let offset = 0;
55
- let hash: bigint;
56
-
57
- if (length >= 32) {
58
- let v1 = toUint64(seed + PRIME1 + PRIME2);
59
- let v2 = toUint64(seed + PRIME2);
60
- let v3 = toUint64(seed);
61
- let v4 = toUint64(seed - PRIME1);
62
-
63
- while (offset <= length - 32) {
64
- v1 = round64(v1, readUint64LE(view, offset));
65
- v2 = round64(v2, readUint64LE(view, offset + 8));
66
- v3 = round64(v3, readUint64LE(view, offset + 16));
67
- v4 = round64(v4, readUint64LE(view, offset + 24));
68
- offset += 32;
52
+ const view = new DataView(input.buffer, input.byteOffset, input.byteLength);
53
+ const length = input.byteLength;
54
+ let offset = 0;
55
+ let hash: bigint;
56
+
57
+ if (length >= 32) {
58
+ let v1 = toUint64(seed + PRIME1 + PRIME2);
59
+ let v2 = toUint64(seed + PRIME2);
60
+ let v3 = toUint64(seed);
61
+ let v4 = toUint64(seed - PRIME1);
62
+
63
+ while (offset <= length - 32) {
64
+ v1 = round64(v1, readUint64LE(view, offset));
65
+ v2 = round64(v2, readUint64LE(view, offset + 8));
66
+ v3 = round64(v3, readUint64LE(view, offset + 16));
67
+ v4 = round64(v4, readUint64LE(view, offset + 24));
68
+ offset += 32;
69
+ }
70
+
71
+ hash = toUint64(rotateLeft64(v1, 1) + rotateLeft64(v2, 7) + rotateLeft64(v3, 12) + rotateLeft64(v4, 18));
72
+ hash = mergeRound64(hash, v1);
73
+ hash = mergeRound64(hash, v2);
74
+ hash = mergeRound64(hash, v3);
75
+ hash = mergeRound64(hash, v4);
76
+ } else {
77
+ hash = toUint64(seed + PRIME5);
69
78
  }
70
79
 
71
- hash = toUint64(rotateLeft64(v1, 1) + rotateLeft64(v2, 7) + rotateLeft64(v3, 12) + rotateLeft64(v4, 18));
72
- hash = mergeRound64(hash, v1);
73
- hash = mergeRound64(hash, v2);
74
- hash = mergeRound64(hash, v3);
75
- hash = mergeRound64(hash, v4);
76
- } else {
77
- hash = toUint64(seed + PRIME5);
78
- }
79
-
80
- hash = toUint64(hash + BigInt(length));
81
-
82
- while (offset <= length - 8) {
83
- const lane = round64(0n, readUint64LE(view, offset));
84
- hash ^= lane;
85
- hash = toUint64(rotateLeft64(hash, 27) * PRIME1 + PRIME4);
86
- offset += 8;
87
- }
88
-
89
- if (offset <= length - 4) {
90
- hash ^= toUint64(readUint32LE(view, offset) * PRIME1);
91
- hash = toUint64(rotateLeft64(hash, 23) * PRIME2 + PRIME3);
92
- offset += 4;
93
- }
94
-
95
- while (offset < length) {
96
- hash ^= toUint64(BigInt(view.getUint8(offset)) * PRIME5);
97
- hash = toUint64(rotateLeft64(hash, 11) * PRIME1);
98
- offset += 1;
99
- }
100
-
101
- return avalanche64(hash);
80
+ hash = toUint64(hash + BigInt(length));
81
+
82
+ while (offset <= length - 8) {
83
+ const lane = round64(0n, readUint64LE(view, offset));
84
+ hash ^= lane;
85
+ hash = toUint64(rotateLeft64(hash, 27) * PRIME1 + PRIME4);
86
+ offset += 8;
87
+ }
88
+
89
+ if (offset <= length - 4) {
90
+ hash ^= toUint64(readUint32LE(view, offset) * PRIME1);
91
+ hash = toUint64(rotateLeft64(hash, 23) * PRIME2 + PRIME3);
92
+ offset += 4;
93
+ }
94
+
95
+ while (offset < length) {
96
+ hash ^= toUint64(BigInt(view.getUint8(offset)) * PRIME5);
97
+ hash = toUint64(rotateLeft64(hash, 11) * PRIME1);
98
+ offset += 1;
99
+ }
100
+
101
+ return avalanche64(hash);
102
102
  }
103
103
 
104
104
  export function computeNativeStyleCch(serializedBody: string): string {
105
- const hash = xxHash64(encoder.encode(serializedBody), CCH_SEED);
106
- return (hash & CCH_MASK).toString(16).padStart(5, "0");
105
+ const hash = xxHash64(encoder.encode(serializedBody), CCH_SEED);
106
+ return (hash & CCH_MASK).toString(16).padStart(5, "0");
107
107
  }
108
108
 
109
109
  export function replaceNativeStyleCch(serializedBody: string): string {
110
- const sentinel = `${CCH_FIELD_PREFIX}${CCH_PLACEHOLDER}`;
111
- const fieldIndex = serializedBody.indexOf(sentinel);
112
- if (fieldIndex === -1) {
113
- return serializedBody;
114
- }
115
-
116
- const valueStart = fieldIndex + CCH_FIELD_PREFIX.length;
117
- const valueEnd = valueStart + CCH_PLACEHOLDER.length;
118
- const cch = computeNativeStyleCch(serializedBody);
119
- return `${serializedBody.slice(0, valueStart)}${cch}${serializedBody.slice(valueEnd)}`;
110
+ const sentinel = `${CCH_FIELD_PREFIX}${CCH_PLACEHOLDER}`;
111
+ const fieldIndex = serializedBody.indexOf(sentinel);
112
+ if (fieldIndex === -1) {
113
+ return serializedBody;
114
+ }
115
+
116
+ const valueStart = fieldIndex + CCH_FIELD_PREFIX.length;
117
+ const valueEnd = valueStart + CCH_PLACEHOLDER.length;
118
+ const cch = computeNativeStyleCch(serializedBody);
119
+ return `${serializedBody.slice(0, valueStart)}${cch}${serializedBody.slice(valueEnd)}`;
120
120
  }
@@ -1,40 +1,40 @@
1
1
  import { STAINLESS_HELPER_KEYS } from "../constants.js";
2
2
 
3
3
  export function getStainlessOs(value: NodeJS.Platform): string {
4
- if (value === "darwin") return "MacOS";
5
- if (value === "win32") return "Windows";
6
- if (value === "linux") return "Linux";
7
- return value;
4
+ if (value === "darwin") return "MacOS";
5
+ if (value === "win32") return "Windows";
6
+ if (value === "linux") return "Linux";
7
+ return value;
8
8
  }
9
9
 
10
10
  export function getStainlessArch(value: string): string {
11
- if (value === "x64") return "x64";
12
- if (value === "arm64") return "arm64";
13
- return value;
11
+ if (value === "x64") return "x64";
12
+ if (value === "arm64") return "arm64";
13
+ return value;
14
14
  }
15
15
 
16
16
  export function buildStainlessHelperHeader(tools: unknown[], messages: unknown[]): string {
17
- const helpers = new Set<string>();
17
+ const helpers = new Set<string>();
18
18
 
19
- const collect = (value: unknown): void => {
20
- if (!value || typeof value !== "object") return;
21
- const obj = value as Record<string, unknown>;
19
+ const collect = (value: unknown): void => {
20
+ if (!value || typeof value !== "object") return;
21
+ const obj = value as Record<string, unknown>;
22
22
 
23
- for (const key of STAINLESS_HELPER_KEYS) {
24
- if (typeof obj[key] === "string" && obj[key]) {
25
- helpers.add(obj[key] as string);
26
- }
27
- }
23
+ for (const key of STAINLESS_HELPER_KEYS) {
24
+ if (typeof obj[key] === "string" && obj[key]) {
25
+ helpers.add(obj[key] as string);
26
+ }
27
+ }
28
28
 
29
- if (Array.isArray(obj.content)) {
30
- for (const contentBlock of obj.content) {
31
- collect(contentBlock);
32
- }
33
- }
34
- };
29
+ if (Array.isArray(obj.content)) {
30
+ for (const contentBlock of obj.content) {
31
+ collect(contentBlock);
32
+ }
33
+ }
34
+ };
35
35
 
36
- for (const tool of tools) collect(tool);
37
- for (const message of messages) collect(message);
36
+ for (const tool of tools) collect(tool);
37
+ for (const message of messages) collect(message);
38
38
 
39
- return Array.from(helpers).join(", ");
39
+ return Array.from(helpers).join(", ");
40
40
  }