@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
@@ -34,9 +34,9 @@
34
34
  // ===========================================================================
35
35
 
36
36
  import {
37
- CLAUDE_CODE_IDENTITY_STRING,
38
- COMPACT_TITLE_GENERATOR_SYSTEM_PROMPT,
39
- KNOWN_IDENTITY_STRINGS,
37
+ CLAUDE_CODE_IDENTITY_STRING,
38
+ COMPACT_TITLE_GENERATOR_SYSTEM_PROMPT,
39
+ KNOWN_IDENTITY_STRINGS,
40
40
  } from "../constants.js";
41
41
  import { buildAnthropicBillingHeader } from "../headers/billing.js";
42
42
  import type { SignatureConfig, SystemBlock } from "../types.js";
@@ -44,47 +44,47 @@ import { dedupeSystemBlocks, isTitleGeneratorSystemBlocks } from "./normalize.js
44
44
  import { compactSystemText, sanitizeSystemText } from "./sanitize.js";
45
45
 
46
46
  export function buildSystemPromptBlocks(
47
- system: SystemBlock[],
48
- signature: SignatureConfig,
49
- messages: unknown[],
47
+ system: SystemBlock[],
48
+ signature: SignatureConfig,
49
+ messages: unknown[],
50
50
  ): SystemBlock[] {
51
- const titleGeneratorRequest = isTitleGeneratorSystemBlocks(system);
51
+ const titleGeneratorRequest = isTitleGeneratorSystemBlocks(system);
52
52
 
53
- let sanitized: SystemBlock[] = system.map((item) => ({
54
- ...item,
55
- text: compactSystemText(
56
- sanitizeSystemText(item.text, signature.sanitizeSystemPrompt === true),
57
- signature.promptCompactionMode,
58
- ),
59
- }));
53
+ let sanitized: SystemBlock[] = system.map((item) => ({
54
+ ...item,
55
+ text: compactSystemText(
56
+ sanitizeSystemText(item.text, signature.sanitizeSystemPrompt === true),
57
+ signature.promptCompactionMode,
58
+ ),
59
+ }));
60
60
 
61
- if (titleGeneratorRequest) {
62
- sanitized = [{ type: "text", text: COMPACT_TITLE_GENERATOR_SYSTEM_PROMPT }];
63
- } else if (signature.promptCompactionMode !== "off") {
64
- sanitized = dedupeSystemBlocks(sanitized);
65
- }
61
+ if (titleGeneratorRequest) {
62
+ sanitized = [{ type: "text", text: COMPACT_TITLE_GENERATOR_SYSTEM_PROMPT }];
63
+ } else if (signature.promptCompactionMode !== "off") {
64
+ sanitized = dedupeSystemBlocks(sanitized);
65
+ }
66
66
 
67
- if (!signature.enabled) {
68
- return sanitized;
69
- }
67
+ if (!signature.enabled) {
68
+ return sanitized;
69
+ }
70
70
 
71
- const filtered = sanitized.filter(
72
- (item) => !item.text.startsWith("x-anthropic-billing-header:") && !KNOWN_IDENTITY_STRINGS.has(item.text),
73
- );
71
+ const filtered = sanitized.filter(
72
+ (item) => !item.text.startsWith("x-anthropic-billing-header:") && !KNOWN_IDENTITY_STRINGS.has(item.text),
73
+ );
74
74
 
75
- const blocks: SystemBlock[] = [];
76
- const billingHeader = buildAnthropicBillingHeader(signature.claudeCliVersion, messages);
77
- if (billingHeader) {
78
- blocks.push({ type: "text", text: billingHeader });
79
- }
75
+ const blocks: SystemBlock[] = [];
76
+ const billingHeader = buildAnthropicBillingHeader(signature.claudeCliVersion, messages);
77
+ if (billingHeader) {
78
+ blocks.push({ type: "text", text: billingHeader });
79
+ }
80
80
 
81
- // CC 2.1.98 sends only {"type":"ephemeral"} — no scope or ttl fields.
82
- blocks.push({
83
- type: "text",
84
- text: CLAUDE_CODE_IDENTITY_STRING,
85
- cache_control: { type: "ephemeral" },
86
- });
87
- blocks.push(...filtered);
81
+ // CC 2.1.98 sends only {"type":"ephemeral"} — no scope or ttl fields.
82
+ blocks.push({
83
+ type: "text",
84
+ text: CLAUDE_CODE_IDENTITY_STRING,
85
+ cache_control: { type: "ephemeral" },
86
+ });
87
+ blocks.push(...filtered);
88
88
 
89
- return blocks;
89
+ return blocks;
90
90
  }
@@ -1,9 +1,9 @@
1
1
  export { buildSystemPromptBlocks } from "./builder.js";
2
2
  export {
3
- dedupeSystemBlocks,
4
- isTitleGeneratorSystemBlocks,
5
- isTitleGeneratorSystemText,
6
- normalizeSystemTextBlocks,
7
- normalizeSystemTextForComparison,
3
+ dedupeSystemBlocks,
4
+ isTitleGeneratorSystemBlocks,
5
+ isTitleGeneratorSystemText,
6
+ normalizeSystemTextBlocks,
7
+ normalizeSystemTextForComparison,
8
8
  } from "./normalize.js";
9
9
  export { compactSystemText, sanitizeSystemText } from "./sanitize.js";
@@ -5,80 +5,80 @@
5
5
  import type { SystemBlock } from "../types.js";
6
6
 
7
7
  export function normalizeSystemTextForComparison(text: string): string {
8
- return text
9
- .replace(/\r\n/g, "\n")
10
- .split("\n")
11
- .map((line) => line.trim())
12
- .join("\n")
13
- .replace(/\n{3,}/g, "\n\n")
14
- .trim();
8
+ return text
9
+ .replace(/\r\n/g, "\n")
10
+ .split("\n")
11
+ .map((line) => line.trim())
12
+ .join("\n")
13
+ .replace(/\n{3,}/g, "\n\n")
14
+ .trim();
15
15
  }
16
16
 
17
17
  export function dedupeSystemBlocks(system: SystemBlock[]): SystemBlock[] {
18
- const exactSeen = new Set<string>();
19
- const exactDeduped: SystemBlock[] = [];
20
-
21
- for (const item of system) {
22
- const normalized = normalizeSystemTextForComparison(item.text);
23
- const key = `${item.type}:${normalized}`;
24
- if (exactSeen.has(key)) continue;
25
- exactSeen.add(key);
26
- exactDeduped.push(item);
27
- }
28
-
29
- const normalizedBlocks = exactDeduped.map((item) => normalizeSystemTextForComparison(item.text));
30
- return exactDeduped.filter((_, index) => {
31
- const current = normalizedBlocks[index];
32
- if (current.length < 80) return true;
33
-
34
- for (let otherIndex = 0; otherIndex < normalizedBlocks.length; otherIndex += 1) {
35
- if (otherIndex === index) continue;
36
- const other = normalizedBlocks[otherIndex];
37
- if (other.length <= current.length + 20) continue;
38
- if (other.includes(current)) return false;
18
+ const exactSeen = new Set<string>();
19
+ const exactDeduped: SystemBlock[] = [];
20
+
21
+ for (const item of system) {
22
+ const normalized = normalizeSystemTextForComparison(item.text);
23
+ const key = `${item.type}:${normalized}`;
24
+ if (exactSeen.has(key)) continue;
25
+ exactSeen.add(key);
26
+ exactDeduped.push(item);
39
27
  }
40
28
 
41
- return true;
42
- });
29
+ const normalizedBlocks = exactDeduped.map((item) => normalizeSystemTextForComparison(item.text));
30
+ return exactDeduped.filter((_, index) => {
31
+ const current = normalizedBlocks[index];
32
+ if (current.length < 80) return true;
33
+
34
+ for (let otherIndex = 0; otherIndex < normalizedBlocks.length; otherIndex += 1) {
35
+ if (otherIndex === index) continue;
36
+ const other = normalizedBlocks[otherIndex];
37
+ if (other.length <= current.length + 20) continue;
38
+ if (other.includes(current)) return false;
39
+ }
40
+
41
+ return true;
42
+ });
43
43
  }
44
44
 
45
45
  export function isTitleGeneratorSystemText(text: string): boolean {
46
- const normalized = text.trim().toLowerCase();
47
- return normalized.includes("you are a title generator") || normalized.includes("generate a brief title");
46
+ const normalized = text.trim().toLowerCase();
47
+ return normalized.includes("you are a title generator") || normalized.includes("generate a brief title");
48
48
  }
49
49
 
50
50
  export function isTitleGeneratorSystemBlocks(system: SystemBlock[]): boolean {
51
- return system.some(
52
- (item) => item.type === "text" && typeof item.text === "string" && isTitleGeneratorSystemText(item.text),
53
- );
51
+ return system.some(
52
+ (item) => item.type === "text" && typeof item.text === "string" && isTitleGeneratorSystemText(item.text),
53
+ );
54
54
  }
55
55
 
56
56
  export function normalizeSystemTextBlocks(system: unknown[] | undefined): SystemBlock[] {
57
- const output: SystemBlock[] = [];
58
- if (!Array.isArray(system)) return output;
59
-
60
- for (const item of system) {
61
- if (typeof item === "string") {
62
- output.push({ type: "text", text: item });
63
- continue;
57
+ const output: SystemBlock[] = [];
58
+ if (!Array.isArray(system)) return output;
59
+
60
+ for (const item of system) {
61
+ if (typeof item === "string") {
62
+ output.push({ type: "text", text: item });
63
+ continue;
64
+ }
65
+
66
+ if (!item || typeof item !== "object") continue;
67
+ const obj = item as Record<string, unknown>;
68
+ if (typeof obj.text !== "string") continue;
69
+
70
+ const normalized: SystemBlock = {
71
+ type: typeof obj.type === "string" ? obj.type : "text",
72
+ text: obj.text,
73
+ };
74
+
75
+ // Intentionally strip cache_control from incoming system blocks.
76
+ // The plugin controls cache placement: only the identity block gets
77
+ // cache_control (added in buildSystemPromptBlocks). Passing through
78
+ // upstream markers causes "maximum of 4 blocks with cache_control" errors.
79
+
80
+ output.push(normalized);
64
81
  }
65
82
 
66
- if (!item || typeof item !== "object") continue;
67
- const obj = item as Record<string, unknown>;
68
- if (typeof obj.text !== "string") continue;
69
-
70
- const normalized: SystemBlock = {
71
- type: typeof obj.type === "string" ? obj.type : "text",
72
- text: obj.text,
73
- };
74
-
75
- // Intentionally strip cache_control from incoming system blocks.
76
- // The plugin controls cache placement: only the identity block gets
77
- // cache_control (added in buildSystemPromptBlocks). Passing through
78
- // upstream markers causes "maximum of 4 blocks with cache_control" errors.
79
-
80
- output.push(normalized);
81
- }
82
-
83
- return output;
83
+ return output;
84
84
  }
@@ -17,41 +17,41 @@ import type { PromptCompactionMode } from "../types.js";
17
17
  * `opencode-anthropic-fix` or `/path/to/opencode/dist`.
18
18
  */
19
19
  export function sanitizeSystemText(text: string, enabled = false): string {
20
- if (!enabled) return text;
21
- return text
22
- .replace(/(?<![\w\-/])OpenCode(?![\w\-/])/g, "Claude Code")
23
- .replace(/(?<![\w\-/])opencode(?![\w\-/])/gi, "Claude")
24
- .replace(/OhMyClaude\s*Code/gi, "Claude Code")
25
- .replace(/OhMyClaudeCode/gi, "Claude Code")
26
- .replace(/(?<![\w\-/])Sisyphus(?![\w\-/])/g, "Claude Code Agent")
27
- .replace(/(?<![\w\-/])Morph\s+plugin(?![\w\-/])/gi, "edit plugin")
28
- .replace(/(?<![\w\-/])morph_edit(?![\w\-/])/g, "edit")
29
- .replace(/(?<![\w\-/])morph_/g, "")
30
- .replace(/(?<![\w\-/])OhMyClaude(?![\w\-/])/gi, "Claude");
20
+ if (!enabled) return text;
21
+ return text
22
+ .replace(/(?<![\w\-/])OpenCode(?![\w\-/])/g, "Claude Code")
23
+ .replace(/(?<![\w\-/])opencode(?![\w\-/])/gi, "Claude")
24
+ .replace(/OhMyClaude\s*Code/gi, "Claude Code")
25
+ .replace(/OhMyClaudeCode/gi, "Claude Code")
26
+ .replace(/(?<![\w\-/])Sisyphus(?![\w\-/])/g, "Claude Code Agent")
27
+ .replace(/(?<![\w\-/])Morph\s+plugin(?![\w\-/])/gi, "edit plugin")
28
+ .replace(/(?<![\w\-/])morph_edit(?![\w\-/])/g, "edit")
29
+ .replace(/(?<![\w\-/])morph_/g, "")
30
+ .replace(/(?<![\w\-/])OhMyClaude(?![\w\-/])/gi, "Claude");
31
31
  }
32
32
 
33
33
  export function compactSystemText(text: string, mode: PromptCompactionMode): string {
34
- const withoutDuplicateIdentityPrefix = text.startsWith(`${CLAUDE_CODE_IDENTITY_STRING}\n`)
35
- ? text.slice(CLAUDE_CODE_IDENTITY_STRING.length).trimStart()
36
- : text;
34
+ const withoutDuplicateIdentityPrefix = text.startsWith(`${CLAUDE_CODE_IDENTITY_STRING}\n`)
35
+ ? text.slice(CLAUDE_CODE_IDENTITY_STRING.length).trimStart()
36
+ : text;
37
37
 
38
- if (mode === "off") {
39
- return withoutDuplicateIdentityPrefix.trim();
40
- }
38
+ if (mode === "off") {
39
+ return withoutDuplicateIdentityPrefix.trim();
40
+ }
41
41
 
42
- const compacted = withoutDuplicateIdentityPrefix.replace(/<example>[\s\S]*?<\/example>/gi, "\n");
42
+ const compacted = withoutDuplicateIdentityPrefix.replace(/<example>[\s\S]*?<\/example>/gi, "\n");
43
43
 
44
- const dedupedLines: string[] = [];
45
- let prevNormalized = "";
46
- for (const line of compacted.split("\n")) {
47
- const normalized = line.trim();
48
- if (normalized && normalized === prevNormalized) continue;
49
- dedupedLines.push(line);
50
- prevNormalized = normalized;
51
- }
44
+ const dedupedLines: string[] = [];
45
+ let prevNormalized = "";
46
+ for (const line of compacted.split("\n")) {
47
+ const normalized = line.trim();
48
+ if (normalized && normalized === prevNormalized) continue;
49
+ dedupedLines.push(line);
50
+ prevNormalized = normalized;
51
+ }
52
52
 
53
- return dedupedLines
54
- .join("\n")
55
- .replace(/\n{3,}/g, "\n\n")
56
- .trim();
53
+ return dedupedLines
54
+ .join("\n")
55
+ .replace(/\n{3,}/g, "\n\n")
56
+ .trim();
57
57
  }
package/src/thinking.ts CHANGED
@@ -10,16 +10,16 @@ import type { ThinkingEffort } from "./types.ts";
10
10
  * Used when an Opus 4.6 request arrives with the legacy budgetTokens shape.
11
11
  */
12
12
  export function budgetTokensToEffort(budgetTokens: number): ThinkingEffort {
13
- if (budgetTokens <= 1024) return "low";
14
- if (budgetTokens <= 8000) return "medium";
15
- return "high";
13
+ if (budgetTokens <= 1024) return "low";
14
+ if (budgetTokens <= 8000) return "medium";
15
+ return "high";
16
16
  }
17
17
 
18
18
  /**
19
19
  * Validate that a given value is a valid ThinkingEffort string.
20
20
  */
21
21
  export function isValidEffort(value: unknown): value is ThinkingEffort {
22
- return value === "low" || value === "medium" || value === "high";
22
+ return value === "low" || value === "medium" || value === "high";
23
23
  }
24
24
 
25
25
  /**
@@ -33,25 +33,26 @@ export function isValidEffort(value: unknown): value is ThinkingEffort {
33
33
  * 3. Absent / disabled: no transform
34
34
  */
35
35
  export function normalizeThinkingBlock(thinking: unknown, model: string): unknown {
36
- if (!thinking || typeof thinking !== "object" || (thinking as Record<string, unknown>).type !== "enabled") {
37
- return thinking;
38
- }
36
+ if (!thinking || typeof thinking !== "object" || (thinking as Record<string, unknown>).type !== "enabled") {
37
+ return thinking;
38
+ }
39
39
 
40
- if (!isAdaptiveThinkingModel(model)) {
41
- // Older models: pass through unchanged (may have budget_tokens)
42
- return thinking;
43
- }
40
+ if (!isAdaptiveThinkingModel(model)) {
41
+ // Older models: pass through unchanged (may have budget_tokens)
42
+ return thinking;
43
+ }
44
44
 
45
- const t = thinking as Record<string, unknown>;
45
+ const t = thinking as Record<string, unknown>;
46
46
 
47
- // Adaptive thinking models: use adaptive thinking with effort
48
- if (isValidEffort(t.effort)) {
49
- // Already in adaptive shape — just strip any legacy budget_tokens field
50
- const { budget_tokens: _dropped, ...rest } = t;
51
- return rest;
52
- }
47
+ // Adaptive thinking models: use adaptive thinking with effort
48
+ if (isValidEffort(t.effort)) {
49
+ // Already in adaptive shape — just strip any legacy budget_tokens field
50
+ const { budget_tokens: _dropped, ...rest } = t;
51
+ return rest;
52
+ }
53
53
 
54
- const effort: ThinkingEffort = typeof t.budget_tokens === "number" ? budgetTokensToEffort(t.budget_tokens) : "medium"; // v2.1.68 default
54
+ const effort: ThinkingEffort =
55
+ typeof t.budget_tokens === "number" ? budgetTokensToEffort(t.budget_tokens) : "medium"; // v2.1.68 default
55
56
 
56
- return { type: "enabled", effort };
57
+ return { type: "enabled", effort };
57
58
  }