@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.
- package/README.md +88 -88
- package/dist/opencode-anthropic-auth-cli.mjs +804 -507
- package/dist/opencode-anthropic-auth-plugin.js +4751 -4109
- package/package.json +67 -59
- package/src/__tests__/billing-edge-cases.test.ts +59 -59
- package/src/__tests__/bun-proxy.parallel.test.ts +388 -382
- package/src/__tests__/cc-comparison.test.ts +87 -87
- package/src/__tests__/cc-credentials.test.ts +254 -250
- package/src/__tests__/cch-drift-checker.test.ts +51 -51
- package/src/__tests__/cch-native-style.test.ts +56 -56
- package/src/__tests__/debug-gating.test.ts +42 -42
- package/src/__tests__/decomposition-smoke.test.ts +68 -68
- package/src/__tests__/fingerprint-regression.test.ts +575 -566
- package/src/__tests__/helpers/conversation-history.smoke.test.ts +271 -271
- package/src/__tests__/helpers/conversation-history.ts +119 -119
- package/src/__tests__/helpers/deferred.smoke.test.ts +103 -103
- package/src/__tests__/helpers/deferred.ts +69 -69
- package/src/__tests__/helpers/in-memory-storage.smoke.test.ts +155 -155
- package/src/__tests__/helpers/in-memory-storage.ts +88 -88
- package/src/__tests__/helpers/mock-bun-proxy.smoke.test.ts +68 -68
- package/src/__tests__/helpers/mock-bun-proxy.ts +189 -189
- package/src/__tests__/helpers/plugin-fetch-harness.smoke.test.ts +273 -273
- package/src/__tests__/helpers/plugin-fetch-harness.ts +288 -288
- package/src/__tests__/helpers/sse.smoke.test.ts +236 -236
- package/src/__tests__/helpers/sse.ts +209 -209
- package/src/__tests__/index.parallel.test.ts +605 -595
- package/src/__tests__/sanitization-regex.test.ts +112 -112
- package/src/__tests__/state-bounds.test.ts +90 -90
- package/src/account-identity.test.ts +197 -192
- package/src/account-identity.ts +69 -67
- package/src/account-state.test.ts +86 -86
- package/src/account-state.ts +25 -25
- package/src/accounts/matching.test.ts +335 -0
- package/src/accounts/matching.ts +167 -0
- package/src/accounts/persistence.test.ts +345 -0
- package/src/accounts/persistence.ts +432 -0
- package/src/accounts/repair.test.ts +276 -0
- package/src/accounts/repair.ts +407 -0
- package/src/accounts.dedup.test.ts +621 -621
- package/src/accounts.test.ts +933 -929
- package/src/accounts.ts +633 -989
- package/src/backoff.test.ts +345 -345
- package/src/backoff.ts +219 -219
- package/src/betas.ts +124 -124
- package/src/bun-fetch.test.ts +345 -342
- package/src/bun-fetch.ts +424 -424
- package/src/bun-proxy.test.ts +25 -25
- package/src/bun-proxy.ts +209 -209
- package/src/cc-credentials.ts +111 -111
- package/src/circuit-breaker.test.ts +184 -184
- package/src/circuit-breaker.ts +169 -169
- package/src/cli/commands/auth.ts +963 -0
- package/src/cli/commands/config.ts +547 -0
- package/src/cli/formatting.test.ts +406 -0
- package/src/cli/formatting.ts +219 -0
- package/src/cli.ts +255 -2022
- package/src/commands/handlers/betas.ts +100 -0
- package/src/commands/handlers/config.ts +99 -0
- package/src/commands/handlers/files.ts +375 -0
- package/src/commands/oauth-flow.ts +181 -166
- package/src/commands/prompts.ts +61 -61
- package/src/commands/router.test.ts +421 -0
- package/src/commands/router.ts +143 -635
- package/src/config.test.ts +482 -482
- package/src/config.ts +412 -404
- package/src/constants.ts +48 -48
- package/src/drift/cch-constants.ts +95 -95
- package/src/env.ts +111 -105
- package/src/headers/billing.ts +33 -33
- package/src/headers/builder.ts +130 -130
- package/src/headers/cch.ts +75 -75
- package/src/headers/stainless.ts +25 -25
- package/src/headers/user-agent.ts +23 -23
- package/src/index.ts +436 -828
- package/src/models.ts +27 -27
- package/src/oauth.test.ts +102 -102
- package/src/oauth.ts +178 -178
- package/src/parent-pid-watcher.test.ts +148 -148
- package/src/parent-pid-watcher.ts +69 -69
- package/src/plugin-helpers.ts +82 -82
- package/src/refresh-helpers.ts +145 -139
- package/src/refresh-lock.test.ts +94 -94
- package/src/refresh-lock.ts +93 -93
- package/src/request/body.history.test.ts +579 -571
- package/src/request/body.ts +255 -255
- package/src/request/metadata.ts +65 -65
- package/src/request/retry.test.ts +156 -156
- package/src/request/retry.ts +67 -67
- package/src/request/url.ts +21 -21
- package/src/request-orchestration-helpers.ts +648 -0
- package/src/response/index.ts +5 -5
- package/src/response/mcp.ts +58 -58
- package/src/response/streaming.test.ts +313 -311
- package/src/response/streaming.ts +412 -410
- package/src/rotation.test.ts +304 -301
- package/src/rotation.ts +205 -205
- package/src/storage.test.ts +547 -547
- package/src/storage.ts +315 -291
- package/src/system-prompt/builder.ts +38 -38
- package/src/system-prompt/index.ts +5 -5
- package/src/system-prompt/normalize.ts +60 -60
- package/src/system-prompt/sanitize.ts +30 -30
- package/src/thinking.ts +21 -20
- package/src/token-refresh.test.ts +265 -265
- package/src/token-refresh.ts +219 -214
- package/src/types.ts +30 -30
- 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
|
-
|
|
10
|
-
|
|
9
|
+
/** Get the current in-memory snapshot (what the test subject sees) */
|
|
10
|
+
snapshot(): AccountStorage;
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
/** Replace the in-memory snapshot (simulates loading from disk) */
|
|
13
|
+
setSnapshot(data: AccountStorage): void;
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
23
|
-
|
|
22
|
+
/** Mock implementation of loadAccounts - returns disk state */
|
|
23
|
+
loadAccountsMock: () => Promise<AccountStorage | null>;
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
60
|
-
|
|
59
|
+
// Internal "disk" state - what loadAccounts would read from filesystem
|
|
60
|
+
let diskState: AccountStorage | null = initial ?? null;
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
// Internal "memory" state - what the test subject holds after loading
|
|
63
|
+
let memoryState: AccountStorage | null = diskState;
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
139
|
-
|
|
138
|
+
accountOverrides: Array<Partial<AccountMetadata> & { refreshToken: string }>,
|
|
139
|
+
extra: Partial<AccountStorage> = {},
|
|
140
140
|
): AccountStorage {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23
|
+
child.stdout?.on("data", onData);
|
|
26
24
|
|
|
27
|
-
|
|
25
|
+
proxy.simulateStdoutBanner();
|
|
28
26
|
|
|
29
|
-
|
|
27
|
+
expect(onData).not.toHaveBeenCalled();
|
|
30
28
|
|
|
31
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
46
|
-
child.kill("SIGKILL");
|
|
43
|
+
child.on("exit", onExit);
|
|
47
44
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
});
|
|
45
|
+
proxy.simulateExit(0);
|
|
46
|
+
child.kill("SIGKILL");
|
|
51
47
|
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
expect(onExit).toHaveBeenCalledWith(0, null);
|
|
49
|
+
expect(proxy.child.killSignals).toEqual(["SIGKILL"]);
|
|
50
|
+
});
|
|
54
51
|
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
});
|