opencode-anthropic-fix 0.2.1 → 0.3.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 (39) hide show
  1. package/README.md +24 -0
  2. package/index.mjs +155 -149
  3. package/lib/config.mjs +47 -25
  4. package/lib/mimicry/adapter-input.mjs +671 -0
  5. package/lib/mimicry/context-hint-threshold.mjs +127 -0
  6. package/lib/mimicry/headers.mjs +71 -64
  7. package/lib/mimicry/request-body.mjs +23 -22
  8. package/lib/mimicry/system-prompt.mjs +54 -23
  9. package/lib/mimicry/wire-compat.mjs +305 -0
  10. package/package.json +3 -3
  11. package/lib/account-state.test.mjs +0 -109
  12. package/lib/accounts.test.mjs +0 -912
  13. package/lib/adaptive-thinking-models.test.mjs +0 -30
  14. package/lib/backoff.test.mjs +0 -411
  15. package/lib/cc-credentials.test.mjs +0 -391
  16. package/lib/config.test.mjs +0 -764
  17. package/lib/context-hint-persist.mjs +0 -68
  18. package/lib/diagnose.test.mjs +0 -122
  19. package/lib/env.test.mjs +0 -25
  20. package/lib/haiku-call.test.mjs +0 -72
  21. package/lib/message-transform.test.mjs +0 -173
  22. package/lib/mimicry/cache.test.mjs +0 -112
  23. package/lib/mimicry/headers.test.mjs +0 -50
  24. package/lib/mimicry/models.test.mjs +0 -132
  25. package/lib/mimicry/request-body.test.mjs +0 -34
  26. package/lib/mimicry/request-helpers.test.mjs +0 -155
  27. package/lib/mimicry/response-stream.test.mjs +0 -127
  28. package/lib/mimicry/system-prompt.test.mjs +0 -73
  29. package/lib/oauth.test.mjs +0 -343
  30. package/lib/redact.test.mjs +0 -154
  31. package/lib/refresh-lock.test.mjs +0 -131
  32. package/lib/request-headers.test.mjs +0 -164
  33. package/lib/retry/overload-loop.test.mjs +0 -107
  34. package/lib/rotation.test.mjs +0 -392
  35. package/lib/session-metrics.test.mjs +0 -67
  36. package/lib/storage.test.mjs +0 -696
  37. package/lib/token-economy/microcompact.test.mjs +0 -28
  38. package/lib/token-economy/transforms.test.mjs +0 -192
  39. package/lib/tuning.test.mjs +0 -89
@@ -1,391 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
- import {
3
- readCCCredentialsFromKeychain,
4
- readCCCredentialsFromFile,
5
- readCCCredentials,
6
- parseCCCredentialData,
7
- } from "./cc-credentials.mjs";
8
-
9
- // ---------------------------------------------------------------------------
10
- // Mocks
11
- // ---------------------------------------------------------------------------
12
-
13
- vi.mock("node:child_process", () => ({
14
- execSync: vi.fn(),
15
- execFileSync: vi.fn(),
16
- }));
17
-
18
- vi.mock("node:fs", () => ({
19
- readFileSync: vi.fn(),
20
- existsSync: vi.fn(),
21
- }));
22
-
23
- vi.mock("node:os", () => ({
24
- homedir: vi.fn(() => "/home/testuser"),
25
- }));
26
-
27
- import { execSync, execFileSync } from "node:child_process";
28
- import { readFileSync, existsSync } from "node:fs";
29
- import { homedir } from "node:os";
30
-
31
- // ---------------------------------------------------------------------------
32
- // Fixtures
33
- // ---------------------------------------------------------------------------
34
-
35
- const VALID_CRED = {
36
- accessToken: "acc-123",
37
- refreshToken: "ref-456",
38
- expiresAt: Date.now() + 3_600_000,
39
- subscriptionType: "claude_pro",
40
- };
41
-
42
- const VALID_CRED_2 = {
43
- accessToken: "acc-789",
44
- refreshToken: "ref-012",
45
- expiresAt: Date.now() + 7_200_000,
46
- };
47
-
48
- const MCP_ONLY_CRED = {
49
- accessToken: "mcp-acc",
50
- refreshToken: "mcp-ref",
51
- expiresAt: Date.now() + 3_600_000,
52
- scope: "mcp",
53
- };
54
-
55
- const KEYCHAIN_DUMP_SINGLE = `
56
- keychain: "/Users/test/Library/Keychains/login.keychain-db"
57
- 0x00000007 <blob>="Claude Code-credentials"
58
- "svce"<blob>="Claude Code-credentials"
59
- `;
60
-
61
- const KEYCHAIN_DUMP_MULTI = `
62
- keychain: "/Users/test/Library/Keychains/login.keychain-db"
63
- "svce"<blob>="Claude Code-credentials-0"
64
- "svce"<blob>="Claude Code-credentials-1"
65
- "svce"<blob>="Claude Code-credentials-0"
66
- `;
67
-
68
- // ---------------------------------------------------------------------------
69
- // parseCCCredentialData
70
- // ---------------------------------------------------------------------------
71
-
72
- describe("parseCCCredentialData", () => {
73
- it("parses valid flat JSON", () => {
74
- const result = parseCCCredentialData(JSON.stringify(VALID_CRED), "cc-file", "test");
75
- expect(result).not.toBeNull();
76
- expect(result.accessToken).toBe("acc-123");
77
- expect(result.refreshToken).toBe("ref-456");
78
- expect(result.subscriptionType).toBe("claude_pro");
79
- expect(result.source).toBe("cc-file");
80
- expect(result.label).toBe("test");
81
- });
82
-
83
- it("parses wrapped claudeAiOauth format", () => {
84
- const wrapped = { claudeAiOauth: VALID_CRED };
85
- const result = parseCCCredentialData(JSON.stringify(wrapped), "cc-keychain", "kc");
86
- expect(result).not.toBeNull();
87
- expect(result.accessToken).toBe("acc-123");
88
- expect(result.source).toBe("cc-keychain");
89
- });
90
-
91
- it("returns null for invalid JSON", () => {
92
- expect(parseCCCredentialData("not json", "cc-file", "x")).toBeNull();
93
- });
94
-
95
- it("returns null for missing accessToken", () => {
96
- const data = { refreshToken: "ref" };
97
- expect(parseCCCredentialData(JSON.stringify(data), "cc-file", "x")).toBeNull();
98
- });
99
-
100
- it("returns null for missing refreshToken", () => {
101
- const data = { accessToken: "acc" };
102
- expect(parseCCCredentialData(JSON.stringify(data), "cc-file", "x")).toBeNull();
103
- });
104
-
105
- it("returns null for non-object JSON", () => {
106
- expect(parseCCCredentialData('"hello"', "cc-file", "x")).toBeNull();
107
- expect(parseCCCredentialData("42", "cc-file", "x")).toBeNull();
108
- expect(parseCCCredentialData("null", "cc-file", "x")).toBeNull();
109
- });
110
-
111
- it("handles expiresAt as ISO string", () => {
112
- const data = { ...VALID_CRED, expiresAt: "2026-06-15T12:00:00Z" };
113
- const result = parseCCCredentialData(JSON.stringify(data), "cc-file", "x");
114
- expect(result).not.toBeNull();
115
- expect(result.expiresAt).toBe(Date.parse("2026-06-15T12:00:00Z"));
116
- });
117
-
118
- it("handles expires_at fallback field", () => {
119
- const data = { accessToken: "a", refreshToken: "r", expires_at: 999999 };
120
- const result = parseCCCredentialData(JSON.stringify(data), "cc-file", "x");
121
- expect(result).not.toBeNull();
122
- expect(result.expiresAt).toBe(999999);
123
- });
124
-
125
- it("defaults expiresAt to 0 when missing", () => {
126
- const data = { accessToken: "a", refreshToken: "r" };
127
- const result = parseCCCredentialData(JSON.stringify(data), "cc-file", "x");
128
- expect(result.expiresAt).toBe(0);
129
- });
130
- });
131
-
132
- // ---------------------------------------------------------------------------
133
- // readCCCredentialsFromKeychain
134
- // ---------------------------------------------------------------------------
135
-
136
- describe("readCCCredentialsFromKeychain", () => {
137
- const origPlatform = process.platform;
138
-
139
- afterEach(() => {
140
- Object.defineProperty(process, "platform", { value: origPlatform });
141
- vi.resetAllMocks();
142
- });
143
-
144
- it("returns empty on non-darwin platforms", () => {
145
- Object.defineProperty(process, "platform", { value: "linux" });
146
- expect(readCCCredentialsFromKeychain()).toEqual([]);
147
- expect(execSync).not.toHaveBeenCalled();
148
- });
149
-
150
- it("reads single keychain service on macOS", () => {
151
- Object.defineProperty(process, "platform", { value: "darwin" });
152
-
153
- // dump-keychain uses execSync, find-generic-password uses execFileSync
154
- execSync.mockImplementation((cmd) => {
155
- if (cmd === "security dump-keychain") return KEYCHAIN_DUMP_SINGLE;
156
- throw new Error("unexpected execSync cmd");
157
- });
158
- execFileSync.mockImplementation((_bin, args) => {
159
- if (args && args.includes("find-generic-password")) return JSON.stringify(VALID_CRED);
160
- throw new Error("unexpected execFileSync cmd");
161
- });
162
-
163
- const result = readCCCredentialsFromKeychain();
164
- expect(result).toHaveLength(1);
165
- expect(result[0].accessToken).toBe("acc-123");
166
- expect(result[0].source).toBe("cc-keychain");
167
- expect(result[0].label).toContain("keychain:");
168
- });
169
-
170
- it("reads multiple keychain services and deduplicates service names", () => {
171
- Object.defineProperty(process, "platform", { value: "darwin" });
172
-
173
- let callIdx = 0;
174
- execSync.mockImplementation((cmd) => {
175
- if (cmd === "security dump-keychain") return KEYCHAIN_DUMP_MULTI;
176
- throw new Error("unexpected execSync cmd");
177
- });
178
- execFileSync.mockImplementation((_bin, args) => {
179
- if (args && args.includes("find-generic-password")) {
180
- callIdx++;
181
- const cred = callIdx === 1 ? VALID_CRED : VALID_CRED_2;
182
- return JSON.stringify(cred);
183
- }
184
- throw new Error("unexpected execFileSync cmd");
185
- });
186
-
187
- const result = readCCCredentialsFromKeychain();
188
- // KEYCHAIN_DUMP_MULTI has credentials-0 twice, but should be deduped to 2 services
189
- expect(result).toHaveLength(2);
190
- expect(result[0].accessToken).toBe("acc-123");
191
- expect(result[1].accessToken).toBe("acc-789");
192
- });
193
-
194
- it("returns empty when dump-keychain throws", () => {
195
- Object.defineProperty(process, "platform", { value: "darwin" });
196
- execSync.mockImplementation(() => {
197
- throw new Error("no keychain");
198
- });
199
-
200
- expect(readCCCredentialsFromKeychain()).toEqual([]);
201
- });
202
-
203
- it("skips services where find-generic-password fails (exit 44)", () => {
204
- Object.defineProperty(process, "platform", { value: "darwin" });
205
-
206
- execSync.mockImplementation((cmd) => {
207
- if (cmd === "security dump-keychain") return KEYCHAIN_DUMP_SINGLE;
208
- throw new Error("unexpected");
209
- });
210
- execFileSync.mockImplementation(() => {
211
- const err = new Error("not found");
212
- err.status = 44;
213
- throw err;
214
- });
215
-
216
- expect(readCCCredentialsFromKeychain()).toEqual([]);
217
- });
218
- });
219
-
220
- // ---------------------------------------------------------------------------
221
- // readCCCredentialsFromFile
222
- // ---------------------------------------------------------------------------
223
-
224
- describe("readCCCredentialsFromFile", () => {
225
- beforeEach(() => {
226
- vi.resetAllMocks();
227
- homedir.mockReturnValue("/home/testuser");
228
- });
229
-
230
- it("returns empty when credentials file does not exist", () => {
231
- existsSync.mockReturnValue(false);
232
- expect(readCCCredentialsFromFile()).toEqual([]);
233
- });
234
-
235
- it("reads flat credential object", () => {
236
- existsSync.mockReturnValue(true);
237
- readFileSync.mockReturnValue(JSON.stringify(VALID_CRED));
238
-
239
- const result = readCCCredentialsFromFile();
240
- expect(result).toHaveLength(1);
241
- expect(result[0].accessToken).toBe("acc-123");
242
- expect(result[0].source).toBe("cc-file");
243
- expect(result[0].label).toBe("file:entry-0");
244
- });
245
-
246
- it("reads wrapped claudeAiOauth format", () => {
247
- existsSync.mockReturnValue(true);
248
- readFileSync.mockReturnValue(JSON.stringify({ claudeAiOauth: VALID_CRED }));
249
-
250
- const result = readCCCredentialsFromFile();
251
- expect(result).toHaveLength(1);
252
- expect(result[0].accessToken).toBe("acc-123");
253
- });
254
-
255
- it("reads array of credentials", () => {
256
- existsSync.mockReturnValue(true);
257
- readFileSync.mockReturnValue(JSON.stringify([VALID_CRED, VALID_CRED_2]));
258
-
259
- const result = readCCCredentialsFromFile();
260
- expect(result).toHaveLength(2);
261
- expect(result[0].accessToken).toBe("acc-123");
262
- expect(result[1].accessToken).toBe("acc-789");
263
- });
264
-
265
- it("filters MCP-only entries", () => {
266
- existsSync.mockReturnValue(true);
267
- readFileSync.mockReturnValue(JSON.stringify([VALID_CRED, MCP_ONLY_CRED]));
268
-
269
- const result = readCCCredentialsFromFile();
270
- expect(result).toHaveLength(1);
271
- expect(result[0].accessToken).toBe("acc-123");
272
- });
273
-
274
- it("skips non-object entries in array", () => {
275
- existsSync.mockReturnValue(true);
276
- readFileSync.mockReturnValue(JSON.stringify([null, 42, "hello", VALID_CRED]));
277
-
278
- const result = readCCCredentialsFromFile();
279
- expect(result).toHaveLength(1);
280
- expect(result[0].accessToken).toBe("acc-123");
281
- });
282
-
283
- it("returns empty on invalid JSON", () => {
284
- existsSync.mockReturnValue(true);
285
- readFileSync.mockReturnValue("not json at all");
286
-
287
- expect(readCCCredentialsFromFile()).toEqual([]);
288
- });
289
-
290
- it("returns empty on read error", () => {
291
- existsSync.mockReturnValue(true);
292
- readFileSync.mockImplementation(() => {
293
- throw new Error("EACCES");
294
- });
295
-
296
- expect(readCCCredentialsFromFile()).toEqual([]);
297
- });
298
-
299
- it("handles entries missing required fields gracefully", () => {
300
- existsSync.mockReturnValue(true);
301
- readFileSync.mockReturnValue(JSON.stringify([{ accessToken: "only-access" }, VALID_CRED]));
302
-
303
- const result = readCCCredentialsFromFile();
304
- expect(result).toHaveLength(1);
305
- expect(result[0].accessToken).toBe("acc-123");
306
- });
307
- });
308
-
309
- // ---------------------------------------------------------------------------
310
- // readCCCredentials (unified)
311
- // ---------------------------------------------------------------------------
312
-
313
- describe("readCCCredentials", () => {
314
- const origPlatform = process.platform;
315
-
316
- beforeEach(() => {
317
- vi.resetAllMocks();
318
- homedir.mockReturnValue("/home/testuser");
319
- });
320
-
321
- afterEach(() => {
322
- Object.defineProperty(process, "platform", { value: origPlatform });
323
- });
324
-
325
- it("combines keychain and file sources on macOS", () => {
326
- Object.defineProperty(process, "platform", { value: "darwin" });
327
-
328
- // Keychain: dump-keychain via execSync, find-generic-password via execFileSync
329
- execSync.mockImplementation((cmd) => {
330
- if (cmd === "security dump-keychain") return KEYCHAIN_DUMP_SINGLE;
331
- throw new Error("unexpected execSync cmd");
332
- });
333
- execFileSync.mockImplementation((_bin, args) => {
334
- if (args && args.includes("find-generic-password")) return JSON.stringify(VALID_CRED);
335
- throw new Error("unexpected execFileSync cmd");
336
- });
337
-
338
- // File returns a different credential
339
- existsSync.mockReturnValue(true);
340
- readFileSync.mockReturnValue(JSON.stringify(VALID_CRED_2));
341
-
342
- const result = readCCCredentials();
343
- expect(result).toHaveLength(2);
344
- expect(result.some((c) => c.source === "cc-keychain")).toBe(true);
345
- expect(result.some((c) => c.source === "cc-file")).toBe(true);
346
- });
347
-
348
- it("deduplicates by refreshToken, preferring keychain", () => {
349
- Object.defineProperty(process, "platform", { value: "darwin" });
350
-
351
- // Both sources return same refreshToken
352
- const keychainCred = { ...VALID_CRED, accessToken: "keychain-token" };
353
- const fileCred = { ...VALID_CRED, accessToken: "file-token" };
354
-
355
- execSync.mockImplementation((cmd) => {
356
- if (cmd === "security dump-keychain") return KEYCHAIN_DUMP_SINGLE;
357
- throw new Error("unexpected execSync cmd");
358
- });
359
- execFileSync.mockImplementation((_bin, args) => {
360
- if (args && args.includes("find-generic-password")) return JSON.stringify(keychainCred);
361
- throw new Error("unexpected execFileSync cmd");
362
- });
363
-
364
- existsSync.mockReturnValue(true);
365
- readFileSync.mockReturnValue(JSON.stringify(fileCred));
366
-
367
- const result = readCCCredentials();
368
- expect(result).toHaveLength(1);
369
- expect(result[0].source).toBe("cc-keychain");
370
- expect(result[0].accessToken).toBe("keychain-token");
371
- });
372
-
373
- it("returns only file credentials on Linux", () => {
374
- Object.defineProperty(process, "platform", { value: "linux" });
375
-
376
- existsSync.mockReturnValue(true);
377
- readFileSync.mockReturnValue(JSON.stringify(VALID_CRED));
378
-
379
- const result = readCCCredentials();
380
- expect(result).toHaveLength(1);
381
- expect(result[0].source).toBe("cc-file");
382
- expect(execSync).not.toHaveBeenCalled();
383
- });
384
-
385
- it("returns empty when no sources available", () => {
386
- Object.defineProperty(process, "platform", { value: "linux" });
387
- existsSync.mockReturnValue(false);
388
-
389
- expect(readCCCredentials()).toEqual([]);
390
- });
391
- });