@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
@@ -6,24 +6,24 @@ import type { AccountMetadata, AccountStorage } from "../../storage.js";
6
6
  * concurrent access scenarios without touching the real filesystem.
7
7
  */
8
8
  export interface InMemoryStorage {
9
- /** Get the current in-memory snapshot (what the test subject sees) */
10
- snapshot(): AccountStorage;
9
+ /** Get the current in-memory snapshot (what the test subject sees) */
10
+ snapshot(): AccountStorage;
11
11
 
12
- /** Replace the in-memory snapshot (simulates loading from disk) */
13
- setSnapshot(data: AccountStorage): void;
12
+ /** Replace the in-memory snapshot (simulates loading from disk) */
13
+ setSnapshot(data: AccountStorage): void;
14
14
 
15
- /**
16
- * Mutate the "disk" state without affecting the caller's snapshot.
17
- * This simulates another process writing to the storage file while
18
- * the test subject holds an in-memory copy.
19
- */
20
- mutateDiskOnly(mutator: (disk: AccountStorage) => AccountStorage): void;
15
+ /**
16
+ * Mutate the "disk" state without affecting the caller's snapshot.
17
+ * This simulates another process writing to the storage file while
18
+ * the test subject holds an in-memory copy.
19
+ */
20
+ mutateDiskOnly(mutator: (disk: AccountStorage) => AccountStorage): void;
21
21
 
22
- /** Mock implementation of loadAccounts - returns disk state */
23
- loadAccountsMock: () => Promise<AccountStorage | null>;
22
+ /** Mock implementation of loadAccounts - returns disk state */
23
+ loadAccountsMock: () => Promise<AccountStorage | null>;
24
24
 
25
- /** Mock implementation of saveAccounts - writes to disk */
26
- saveAccountsMock: (data: AccountStorage) => Promise<void>;
25
+ /** Mock implementation of saveAccounts - writes to disk */
26
+ saveAccountsMock: (data: AccountStorage) => Promise<void>;
27
27
  }
28
28
 
29
29
  /**
@@ -56,97 +56,97 @@ export interface InMemoryStorage {
56
56
  * ```
57
57
  */
58
58
  export function createInMemoryStorage(initial?: AccountStorage): InMemoryStorage {
59
- // Internal "disk" state - what loadAccounts would read from filesystem
60
- let diskState: AccountStorage | null = initial ?? null;
59
+ // Internal "disk" state - what loadAccounts would read from filesystem
60
+ let diskState: AccountStorage | null = initial ?? null;
61
61
 
62
- // Internal "memory" state - what the test subject holds after loading
63
- let memoryState: AccountStorage | null = diskState;
62
+ // Internal "memory" state - what the test subject holds after loading
63
+ let memoryState: AccountStorage | null = diskState;
64
64
 
65
- return {
66
- snapshot(): AccountStorage {
67
- if (memoryState === null) {
68
- throw new Error("Storage snapshot is null - did you forget to set initial data or call setSnapshot()?");
69
- }
70
- // Return deep copy to prevent accidental mutations via references
71
- return structuredClone(memoryState);
72
- },
65
+ return {
66
+ snapshot(): AccountStorage {
67
+ if (memoryState === null) {
68
+ throw new Error("Storage snapshot is null - did you forget to set initial data or call setSnapshot()?");
69
+ }
70
+ // Return deep copy to prevent accidental mutations via references
71
+ return structuredClone(memoryState);
72
+ },
73
73
 
74
- setSnapshot(data: AccountStorage): void {
75
- // Update both disk and memory to match
76
- diskState = structuredClone(data);
77
- memoryState = structuredClone(data);
78
- },
74
+ setSnapshot(data: AccountStorage): void {
75
+ // Update both disk and memory to match
76
+ diskState = structuredClone(data);
77
+ memoryState = structuredClone(data);
78
+ },
79
79
 
80
- mutateDiskOnly(mutator: (disk: AccountStorage) => AccountStorage): void {
81
- if (diskState === null) {
82
- throw new Error("Cannot mutate disk - disk state is null. Set initial data first.");
83
- }
84
- // Only mutate disk state, leaving memoryState unchanged
85
- // This simulates another process writing to the file
86
- diskState = mutator(structuredClone(diskState));
87
- },
80
+ mutateDiskOnly(mutator: (disk: AccountStorage) => AccountStorage): void {
81
+ if (diskState === null) {
82
+ throw new Error("Cannot mutate disk - disk state is null. Set initial data first.");
83
+ }
84
+ // Only mutate disk state, leaving memoryState unchanged
85
+ // This simulates another process writing to the file
86
+ diskState = mutator(structuredClone(diskState));
87
+ },
88
88
 
89
- loadAccountsMock: vi.fn(async (): Promise<AccountStorage | null> => {
90
- // Simulate reading from disk - returns current disk state
91
- return diskState === null ? null : structuredClone(diskState);
92
- }),
89
+ loadAccountsMock: vi.fn(async (): Promise<AccountStorage | null> => {
90
+ // Simulate reading from disk - returns current disk state
91
+ return diskState === null ? null : structuredClone(diskState);
92
+ }),
93
93
 
94
- saveAccountsMock: vi.fn(async (data: AccountStorage): Promise<void> => {
95
- // Simulate writing to disk - updates disk state
96
- diskState = structuredClone(data);
97
- }),
98
- };
94
+ saveAccountsMock: vi.fn(async (data: AccountStorage): Promise<void> => {
95
+ // Simulate writing to disk - updates disk state
96
+ diskState = structuredClone(data);
97
+ }),
98
+ };
99
99
  }
100
100
 
101
101
  /**
102
102
  * Helper to create a minimal valid stored account for testing.
103
103
  */
104
104
  export function makeStoredAccount(overrides: Partial<AccountMetadata> & { refreshToken: string }): AccountMetadata {
105
- const now = Date.now();
106
- return {
107
- id: overrides.id ?? `acct-${Math.random().toString(36).slice(2, 8)}`,
108
- email: overrides.email,
109
- identity: overrides.identity,
110
- label: overrides.label,
111
- refreshToken: overrides.refreshToken,
112
- access: overrides.access ?? "access-token",
113
- expires: overrides.expires ?? now + 3600_000,
114
- addedAt: overrides.addedAt ?? now,
115
- lastUsed: overrides.lastUsed ?? 0,
116
- enabled: overrides.enabled ?? true,
117
- rateLimitResetTimes: overrides.rateLimitResetTimes ?? {},
118
- consecutiveFailures: overrides.consecutiveFailures ?? 0,
119
- lastFailureTime: overrides.lastFailureTime ?? null,
120
- lastSwitchReason: overrides.lastSwitchReason,
121
- token_updated_at: overrides.token_updated_at ?? 0,
122
- stats: overrides.stats ?? {
123
- requests: 0,
124
- inputTokens: 0,
125
- outputTokens: 0,
126
- cacheReadTokens: 0,
127
- cacheWriteTokens: 0,
128
- lastReset: now,
129
- },
130
- source: overrides.source,
131
- };
105
+ const now = Date.now();
106
+ return {
107
+ id: overrides.id ?? `acct-${Math.random().toString(36).slice(2, 8)}`,
108
+ email: overrides.email,
109
+ identity: overrides.identity,
110
+ label: overrides.label,
111
+ refreshToken: overrides.refreshToken,
112
+ access: overrides.access ?? "access-token",
113
+ expires: overrides.expires ?? now + 3600_000,
114
+ addedAt: overrides.addedAt ?? now,
115
+ lastUsed: overrides.lastUsed ?? 0,
116
+ enabled: overrides.enabled ?? true,
117
+ rateLimitResetTimes: overrides.rateLimitResetTimes ?? {},
118
+ consecutiveFailures: overrides.consecutiveFailures ?? 0,
119
+ lastFailureTime: overrides.lastFailureTime ?? null,
120
+ lastSwitchReason: overrides.lastSwitchReason,
121
+ token_updated_at: overrides.token_updated_at ?? 0,
122
+ stats: overrides.stats ?? {
123
+ requests: 0,
124
+ inputTokens: 0,
125
+ outputTokens: 0,
126
+ cacheReadTokens: 0,
127
+ cacheWriteTokens: 0,
128
+ lastReset: now,
129
+ },
130
+ source: overrides.source,
131
+ };
132
132
  }
133
133
 
134
134
  /**
135
135
  * Helper to create a valid storage payload from account overrides.
136
136
  */
137
137
  export function makeAccountsData(
138
- accountOverrides: Array<Partial<AccountMetadata> & { refreshToken: string }>,
139
- extra: Partial<AccountStorage> = {},
138
+ accountOverrides: Array<Partial<AccountMetadata> & { refreshToken: string }>,
139
+ extra: Partial<AccountStorage> = {},
140
140
  ): AccountStorage {
141
- return {
142
- version: 1,
143
- accounts: accountOverrides.map((o, i) =>
144
- makeStoredAccount({
145
- addedAt: (i + 1) * 1000,
146
- ...o,
147
- }),
148
- ),
149
- activeIndex: 0,
150
- ...extra,
151
- };
141
+ return {
142
+ version: 1,
143
+ accounts: accountOverrides.map((o, i) =>
144
+ makeStoredAccount({
145
+ addedAt: (i + 1) * 1000,
146
+ ...o,
147
+ }),
148
+ ),
149
+ activeIndex: 0,
150
+ ...extra,
151
+ };
152
152
  }
@@ -4,89 +4,89 @@ import { createDeferred } from "./deferred";
4
4
  import { createMockBunProxy } from "./mock-bun-proxy";
5
5
 
6
6
  describe("mock bun proxy helper", () => {
7
- beforeEach(() => {
8
- vi.useFakeTimers();
9
- });
10
-
11
- afterEach(() => {
12
- vi.useRealTimers();
13
- vi.restoreAllMocks();
14
- });
15
-
16
- it("mocks spawn and emits the startup banner", async () => {
17
- const proxy = createMockBunProxy({ bannerDelay: 25 });
18
- const child = proxy.mockSpawn("bun", ["run", "./bun-proxy.ts", "48372"], {
19
- stdio: ["ignore", "pipe", "pipe"],
7
+ beforeEach(() => {
8
+ vi.useFakeTimers();
20
9
  });
21
- const onData = vi.fn();
22
10
 
23
- child.stdout?.on("data", onData);
11
+ afterEach(() => {
12
+ vi.useRealTimers();
13
+ vi.restoreAllMocks();
14
+ });
15
+
16
+ it("mocks spawn and emits the startup banner", async () => {
17
+ const proxy = createMockBunProxy({ bannerDelay: 25 });
18
+ const child = proxy.mockSpawn("bun", ["run", "./bun-proxy.ts", "48372"], {
19
+ stdio: ["ignore", "pipe", "pipe"],
20
+ });
21
+ const onData = vi.fn();
24
22
 
25
- proxy.simulateStdoutBanner();
23
+ child.stdout?.on("data", onData);
26
24
 
27
- expect(onData).not.toHaveBeenCalled();
25
+ proxy.simulateStdoutBanner();
28
26
 
29
- await vi.advanceTimersByTimeAsync(25);
27
+ expect(onData).not.toHaveBeenCalled();
30
28
 
31
- expect(proxy.child.pid).toBe(child.pid);
32
- expect(onData).toHaveBeenCalledOnce();
33
- expect(onData.mock.calls[0][0].toString()).toContain("BUN_PROXY_PORT=48372");
34
- });
29
+ await vi.advanceTimersByTimeAsync(25);
35
30
 
36
- it("fires exit handlers and records kill signals", () => {
37
- const proxy = createMockBunProxy();
38
- const child = proxy.mockSpawn("bun", ["run", "./bun-proxy.ts", "48372"], {
39
- stdio: ["ignore", "pipe", "pipe"],
31
+ expect(proxy.child.pid).toBe(child.pid);
32
+ expect(onData).toHaveBeenCalledOnce();
33
+ expect(onData.mock.calls[0][0].toString()).toContain("BUN_PROXY_PORT=48372");
40
34
  });
41
- const onExit = vi.fn();
42
35
 
43
- child.on("exit", onExit);
36
+ it("fires exit handlers and records kill signals", () => {
37
+ const proxy = createMockBunProxy();
38
+ const child = proxy.mockSpawn("bun", ["run", "./bun-proxy.ts", "48372"], {
39
+ stdio: ["ignore", "pipe", "pipe"],
40
+ });
41
+ const onExit = vi.fn();
44
42
 
45
- proxy.simulateExit(0);
46
- child.kill("SIGKILL");
43
+ child.on("exit", onExit);
47
44
 
48
- expect(onExit).toHaveBeenCalledWith(0, null);
49
- expect(proxy.child.killSignals).toEqual(["SIGKILL"]);
50
- });
45
+ proxy.simulateExit(0);
46
+ child.kill("SIGKILL");
51
47
 
52
- it("rejects async spawn callers when configured to throw", async () => {
53
- const proxy = createMockBunProxy({ spawnError: new Error("spawn failed") });
48
+ expect(onExit).toHaveBeenCalledWith(0, null);
49
+ expect(proxy.child.killSignals).toEqual(["SIGKILL"]);
50
+ });
54
51
 
55
- await expect((async () => proxy.mockSpawn("bun", ["run", "./bun-proxy.ts", "48372"], {}))()).rejects.toThrow(
56
- "spawn failed",
57
- );
58
- });
52
+ it("rejects async spawn callers when configured to throw", async () => {
53
+ const proxy = createMockBunProxy({ spawnError: new Error("spawn failed") });
59
54
 
60
- it("tracks in-flight forwarded fetch requests without touching the network", async () => {
61
- const response = createDeferred<Response>();
62
- const forwardToMockFetch = vi.fn(() => response.promise);
63
- const proxy = createMockBunProxy({ forwardToMockFetch });
64
- const child = proxy.mockSpawn("bun", ["run", "./bun-proxy.ts", "48372"], {
65
- stdio: ["ignore", "pipe", "pipe"],
55
+ await expect((async () => proxy.mockSpawn("bun", ["run", "./bun-proxy.ts", "48372"], {}))()).rejects.toThrow(
56
+ "spawn failed",
57
+ );
66
58
  });
67
59
 
68
- const fetchPromise = child.forwardFetch("http://127.0.0.1:48372/", {
69
- method: "POST",
70
- headers: {
71
- "x-proxy-url": "https://api.anthropic.com/v1/messages",
72
- authorization: "Bearer test-token",
73
- connection: "keep-alive",
74
- },
75
- body: JSON.stringify({ hello: "world" }),
60
+ it("tracks in-flight forwarded fetch requests without touching the network", async () => {
61
+ const response = createDeferred<Response>();
62
+ const forwardToMockFetch = vi.fn(() => response.promise);
63
+ const proxy = createMockBunProxy({ forwardToMockFetch });
64
+ const child = proxy.mockSpawn("bun", ["run", "./bun-proxy.ts", "48372"], {
65
+ stdio: ["ignore", "pipe", "pipe"],
66
+ });
67
+
68
+ const fetchPromise = child.forwardFetch("http://127.0.0.1:48372/", {
69
+ method: "POST",
70
+ headers: {
71
+ "x-proxy-url": "https://api.anthropic.com/v1/messages",
72
+ authorization: "Bearer test-token",
73
+ connection: "keep-alive",
74
+ },
75
+ body: JSON.stringify({ hello: "world" }),
76
+ });
77
+
78
+ expect(proxy.getInFlightCount()).toBe(1);
79
+
80
+ response.resolve(new Response("ok", { status: 200 }));
81
+
82
+ await expect(fetchPromise).resolves.toBeInstanceOf(Response);
83
+ expect(proxy.getInFlightCount()).toBe(0);
84
+ expect(forwardToMockFetch).toHaveBeenCalledWith(
85
+ "https://api.anthropic.com/v1/messages",
86
+ expect.objectContaining({
87
+ method: "POST",
88
+ body: JSON.stringify({ hello: "world" }),
89
+ }),
90
+ );
76
91
  });
77
-
78
- expect(proxy.getInFlightCount()).toBe(1);
79
-
80
- response.resolve(new Response("ok", { status: 200 }));
81
-
82
- await expect(fetchPromise).resolves.toBeInstanceOf(Response);
83
- expect(proxy.getInFlightCount()).toBe(0);
84
- expect(forwardToMockFetch).toHaveBeenCalledWith(
85
- "https://api.anthropic.com/v1/messages",
86
- expect.objectContaining({
87
- method: "POST",
88
- body: JSON.stringify({ hello: "world" }),
89
- }),
90
- );
91
- });
92
92
  });