@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
@@ -2,212 +2,217 @@ import { describe, expect, it } from "vitest";
2
2
  import type { CCCredential } from "./cc-credentials.js";
3
3
  import type { ManagedAccount } from "./accounts.js";
4
4
  import {
5
- findByIdentity,
6
- identitiesMatch,
7
- resolveIdentity,
8
- resolveIdentityFromCCCredential,
9
- resolveIdentityFromOAuthExchange,
10
- serializeIdentity,
11
- type AccountIdentity,
5
+ findByIdentity,
6
+ identitiesMatch,
7
+ resolveIdentity,
8
+ resolveIdentityFromCCCredential,
9
+ resolveIdentityFromOAuthExchange,
10
+ serializeIdentity,
11
+ type AccountIdentity,
12
12
  } from "./account-identity.js";
13
13
 
14
14
  describe("account-identity", () => {
15
- const oauthAccount: ManagedAccount = {
16
- id: "oauth-1",
17
- index: 0,
18
- email: "alice@example.com",
19
- refreshToken: "rt_oauth_123",
20
- access: "at_oauth_123",
21
- expires: Date.now() + 3600000,
22
- tokenUpdatedAt: Date.now(),
23
- addedAt: Date.now(),
24
- lastUsed: 0,
25
- enabled: true,
26
- rateLimitResetTimes: {},
27
- consecutiveFailures: 0,
28
- lastFailureTime: null,
29
- stats: {
30
- requests: 0,
31
- inputTokens: 0,
32
- outputTokens: 0,
33
- cacheReadTokens: 0,
34
- cacheWriteTokens: 0,
35
- lastReset: Date.now(),
36
- },
37
- source: "oauth",
38
- };
39
-
40
- const ccAccount: ManagedAccount = {
41
- id: "cc-1",
42
- index: 1,
43
- email: undefined,
44
- label: "Claude Code-credentials:alice@example.com",
45
- refreshToken: "rt_cc_456",
46
- access: "at_cc_456",
47
- expires: Date.now() + 3600000,
48
- tokenUpdatedAt: Date.now(),
49
- addedAt: Date.now(),
50
- lastUsed: 0,
51
- enabled: true,
52
- rateLimitResetTimes: {},
53
- consecutiveFailures: 0,
54
- lastFailureTime: null,
55
- stats: {
56
- requests: 0,
57
- inputTokens: 0,
58
- outputTokens: 0,
59
- cacheReadTokens: 0,
60
- cacheWriteTokens: 0,
61
- lastReset: Date.now(),
62
- },
63
- source: "cc-keychain",
64
- };
65
-
66
- const legacyAccount: ManagedAccount = {
67
- id: "legacy-1",
68
- index: 2,
69
- email: undefined,
70
- refreshToken: "rt_legacy_789",
71
- access: "at_legacy_789",
72
- expires: Date.now() + 3600000,
73
- tokenUpdatedAt: Date.now(),
74
- addedAt: Date.now(),
75
- lastUsed: 0,
76
- enabled: true,
77
- rateLimitResetTimes: {},
78
- consecutiveFailures: 0,
79
- lastFailureTime: null,
80
- stats: {
81
- requests: 0,
82
- inputTokens: 0,
83
- outputTokens: 0,
84
- cacheReadTokens: 0,
85
- cacheWriteTokens: 0,
86
- lastReset: Date.now(),
87
- },
88
- source: undefined,
89
- };
90
-
91
- const ccCredential: CCCredential = {
92
- accessToken: "at_cc_456",
93
- refreshToken: "rt_cc_456",
94
- expiresAt: Date.now() + 3600000,
95
- source: "cc-keychain",
96
- label: "Claude Code-credentials:alice@example.com",
97
- };
98
-
99
- describe("resolveIdentity", () => {
100
- it("should resolve OAuth account identity from email", () => {
101
- const identity = resolveIdentity(oauthAccount);
102
-
103
- expect(identity).toEqual({
104
- kind: "oauth",
15
+ const oauthAccount: ManagedAccount = {
16
+ id: "oauth-1",
17
+ index: 0,
105
18
  email: "alice@example.com",
106
- });
107
- });
108
-
109
- it("should resolve CC account identity from source+label", () => {
110
- const identity = resolveIdentity(ccAccount);
111
-
112
- expect(identity).toEqual({
113
- kind: "cc",
114
- source: "cc-keychain",
19
+ refreshToken: "rt_oauth_123",
20
+ access: "at_oauth_123",
21
+ expires: Date.now() + 3600000,
22
+ tokenUpdatedAt: Date.now(),
23
+ addedAt: Date.now(),
24
+ lastUsed: 0,
25
+ enabled: true,
26
+ rateLimitResetTimes: {},
27
+ consecutiveFailures: 0,
28
+ lastFailureTime: null,
29
+ stats: {
30
+ requests: 0,
31
+ inputTokens: 0,
32
+ outputTokens: 0,
33
+ cacheReadTokens: 0,
34
+ cacheWriteTokens: 0,
35
+ lastReset: Date.now(),
36
+ },
37
+ source: "oauth",
38
+ };
39
+
40
+ const ccAccount: ManagedAccount = {
41
+ id: "cc-1",
42
+ index: 1,
43
+ email: undefined,
115
44
  label: "Claude Code-credentials:alice@example.com",
116
- });
117
- });
118
-
119
- it("should resolve legacy identity from refreshToken when no email or source", () => {
120
- const identity = resolveIdentity(legacyAccount);
45
+ refreshToken: "rt_cc_456",
46
+ access: "at_cc_456",
47
+ expires: Date.now() + 3600000,
48
+ tokenUpdatedAt: Date.now(),
49
+ addedAt: Date.now(),
50
+ lastUsed: 0,
51
+ enabled: true,
52
+ rateLimitResetTimes: {},
53
+ consecutiveFailures: 0,
54
+ lastFailureTime: null,
55
+ stats: {
56
+ requests: 0,
57
+ inputTokens: 0,
58
+ outputTokens: 0,
59
+ cacheReadTokens: 0,
60
+ cacheWriteTokens: 0,
61
+ lastReset: Date.now(),
62
+ },
63
+ source: "cc-keychain",
64
+ };
121
65
 
122
- expect(identity).toEqual({
123
- kind: "legacy",
66
+ const legacyAccount: ManagedAccount = {
67
+ id: "legacy-1",
68
+ index: 2,
69
+ email: undefined,
124
70
  refreshToken: "rt_legacy_789",
125
- });
126
- });
127
- });
128
-
129
- describe("exchange helpers", () => {
130
- it("should resolve CC identity from a Claude Code credential", () => {
131
- expect(resolveIdentityFromCCCredential(ccCredential)).toEqual({
132
- kind: "cc",
71
+ access: "at_legacy_789",
72
+ expires: Date.now() + 3600000,
73
+ tokenUpdatedAt: Date.now(),
74
+ addedAt: Date.now(),
75
+ lastUsed: 0,
76
+ enabled: true,
77
+ rateLimitResetTimes: {},
78
+ consecutiveFailures: 0,
79
+ lastFailureTime: null,
80
+ stats: {
81
+ requests: 0,
82
+ inputTokens: 0,
83
+ outputTokens: 0,
84
+ cacheReadTokens: 0,
85
+ cacheWriteTokens: 0,
86
+ lastReset: Date.now(),
87
+ },
88
+ source: undefined,
89
+ };
90
+
91
+ const ccCredential: CCCredential = {
92
+ accessToken: "at_cc_456",
93
+ refreshToken: "rt_cc_456",
94
+ expiresAt: Date.now() + 3600000,
133
95
  source: "cc-keychain",
134
96
  label: "Claude Code-credentials:alice@example.com",
135
- });
97
+ };
98
+
99
+ describe("resolveIdentity", () => {
100
+ it("should resolve OAuth account identity from email", () => {
101
+ const identity = resolveIdentity(oauthAccount);
102
+
103
+ expect(identity).toEqual({
104
+ kind: "oauth",
105
+ email: "alice@example.com",
106
+ });
107
+ });
108
+
109
+ it("should resolve CC account identity from source+label", () => {
110
+ const identity = resolveIdentity(ccAccount);
111
+
112
+ expect(identity).toEqual({
113
+ kind: "cc",
114
+ source: "cc-keychain",
115
+ label: "Claude Code-credentials:alice@example.com",
116
+ });
117
+ });
118
+
119
+ it("should resolve legacy identity from refreshToken when no email or source", () => {
120
+ const identity = resolveIdentity(legacyAccount);
121
+
122
+ expect(identity).toEqual({
123
+ kind: "legacy",
124
+ refreshToken: "rt_legacy_789",
125
+ });
126
+ });
136
127
  });
137
128
 
138
- it("should resolve OAuth exchange results with email as OAuth identity", () => {
139
- expect(resolveIdentityFromOAuthExchange({ email: "alice@example.com", refresh: "rt_oauth_123" })).toEqual({
140
- kind: "oauth",
141
- email: "alice@example.com",
142
- });
129
+ describe("exchange helpers", () => {
130
+ it("should resolve CC identity from a Claude Code credential", () => {
131
+ expect(resolveIdentityFromCCCredential(ccCredential)).toEqual({
132
+ kind: "cc",
133
+ source: "cc-keychain",
134
+ label: "Claude Code-credentials:alice@example.com",
135
+ });
136
+ });
137
+
138
+ it("should resolve OAuth exchange results with email as OAuth identity", () => {
139
+ expect(resolveIdentityFromOAuthExchange({ email: "alice@example.com", refresh: "rt_oauth_123" })).toEqual({
140
+ kind: "oauth",
141
+ email: "alice@example.com",
142
+ });
143
+ });
144
+
145
+ it("should resolve OAuth exchange results without email as legacy identity", () => {
146
+ expect(resolveIdentityFromOAuthExchange({ refresh: "rt_legacy_789" })).toEqual({
147
+ kind: "legacy",
148
+ refreshToken: "rt_legacy_789",
149
+ });
150
+ });
143
151
  });
144
152
 
145
- it("should resolve OAuth exchange results without email as legacy identity", () => {
146
- expect(resolveIdentityFromOAuthExchange({ refresh: "rt_legacy_789" })).toEqual({
147
- kind: "legacy",
148
- refreshToken: "rt_legacy_789",
149
- });
150
- });
151
- });
152
-
153
- describe("identitiesMatch", () => {
154
- it("should match OAuth accounts with same email", () => {
155
- const identity1: AccountIdentity = { kind: "oauth", email: "alice@example.com" };
156
- const identity2: AccountIdentity = { kind: "oauth", email: "alice@example.com" };
157
-
158
- expect(identitiesMatch(identity1, identity2)).toBe(true);
159
- });
160
-
161
- it("should match CC accounts with same source+label", () => {
162
- const identity1: AccountIdentity = { kind: "cc", source: "cc-keychain", label: "label1" };
163
- const identity2: AccountIdentity = { kind: "cc", source: "cc-keychain", label: "label1" };
164
-
165
- expect(identitiesMatch(identity1, identity2)).toBe(true);
166
- });
167
-
168
- it("should not match different identity types", () => {
169
- const oauthIdentity: AccountIdentity = { kind: "oauth", email: "alice@example.com" };
170
- const ccIdentity: AccountIdentity = { kind: "cc", source: "cc-keychain", label: "label" };
171
- const legacyIdentity: AccountIdentity = { kind: "legacy", refreshToken: "rt" };
172
-
173
- expect(identitiesMatch(oauthIdentity, ccIdentity)).toBe(false);
174
- expect(identitiesMatch(oauthIdentity, legacyIdentity)).toBe(false);
175
- expect(identitiesMatch(ccIdentity, legacyIdentity)).toBe(false);
176
- });
177
-
178
- it("should not match different stable key fields", () => {
179
- expect(
180
- identitiesMatch({ kind: "oauth", email: "alice@example.com" }, { kind: "oauth", email: "bob@example.com" }),
181
- ).toBe(false);
182
- expect(
183
- identitiesMatch(
184
- { kind: "cc", source: "cc-keychain", label: "label1" },
185
- { kind: "cc", source: "cc-keychain", label: "label2" },
186
- ),
187
- ).toBe(false);
188
- });
189
- });
190
-
191
- describe("findByIdentity", () => {
192
- const accounts: ManagedAccount[] = [oauthAccount, ccAccount, legacyAccount];
193
-
194
- it("should find matching accounts by stable identity", () => {
195
- expect(findByIdentity(accounts, { kind: "oauth", email: "alice@example.com" })).toBe(oauthAccount);
196
- expect(
197
- findByIdentity(accounts, {
198
- kind: "cc",
199
- source: "cc-keychain",
200
- label: "Claude Code-credentials:alice@example.com",
201
- }),
202
- ).toBe(ccAccount);
203
- expect(findByIdentity(accounts, { kind: "legacy", refreshToken: "rt_legacy_789" })).toBe(legacyAccount);
204
- expect(findByIdentity(accounts, { kind: "oauth", email: "unknown@example.com" })).toBeNull();
153
+ describe("identitiesMatch", () => {
154
+ it("should match OAuth accounts with same email", () => {
155
+ const identity1: AccountIdentity = { kind: "oauth", email: "alice@example.com" };
156
+ const identity2: AccountIdentity = { kind: "oauth", email: "alice@example.com" };
157
+
158
+ expect(identitiesMatch(identity1, identity2)).toBe(true);
159
+ });
160
+
161
+ it("should match CC accounts with same source+label", () => {
162
+ const identity1: AccountIdentity = { kind: "cc", source: "cc-keychain", label: "label1" };
163
+ const identity2: AccountIdentity = { kind: "cc", source: "cc-keychain", label: "label1" };
164
+
165
+ expect(identitiesMatch(identity1, identity2)).toBe(true);
166
+ });
167
+
168
+ it("should not match different identity types", () => {
169
+ const oauthIdentity: AccountIdentity = { kind: "oauth", email: "alice@example.com" };
170
+ const ccIdentity: AccountIdentity = { kind: "cc", source: "cc-keychain", label: "label" };
171
+ const legacyIdentity: AccountIdentity = { kind: "legacy", refreshToken: "rt" };
172
+
173
+ expect(identitiesMatch(oauthIdentity, ccIdentity)).toBe(false);
174
+ expect(identitiesMatch(oauthIdentity, legacyIdentity)).toBe(false);
175
+ expect(identitiesMatch(ccIdentity, legacyIdentity)).toBe(false);
176
+ });
177
+
178
+ it("should not match different stable key fields", () => {
179
+ expect(
180
+ identitiesMatch(
181
+ { kind: "oauth", email: "alice@example.com" },
182
+ { kind: "oauth", email: "bob@example.com" },
183
+ ),
184
+ ).toBe(false);
185
+ expect(
186
+ identitiesMatch(
187
+ { kind: "cc", source: "cc-keychain", label: "label1" },
188
+ { kind: "cc", source: "cc-keychain", label: "label2" },
189
+ ),
190
+ ).toBe(false);
191
+ });
205
192
  });
206
193
 
207
- it("should serialize identities without leaking secrets", () => {
208
- expect(serializeIdentity({ kind: "oauth", email: "alice@example.com" })).toBe("oauth:alice@example.com");
209
- expect(serializeIdentity({ kind: "cc", source: "cc-keychain", label: "label1" })).toBe("cc:cc-keychain:label1");
210
- expect(serializeIdentity({ kind: "legacy", refreshToken: "secret-refresh-token" })).toBe("legacy:redacted");
194
+ describe("findByIdentity", () => {
195
+ const accounts: ManagedAccount[] = [oauthAccount, ccAccount, legacyAccount];
196
+
197
+ it("should find matching accounts by stable identity", () => {
198
+ expect(findByIdentity(accounts, { kind: "oauth", email: "alice@example.com" })).toBe(oauthAccount);
199
+ expect(
200
+ findByIdentity(accounts, {
201
+ kind: "cc",
202
+ source: "cc-keychain",
203
+ label: "Claude Code-credentials:alice@example.com",
204
+ }),
205
+ ).toBe(ccAccount);
206
+ expect(findByIdentity(accounts, { kind: "legacy", refreshToken: "rt_legacy_789" })).toBe(legacyAccount);
207
+ expect(findByIdentity(accounts, { kind: "oauth", email: "unknown@example.com" })).toBeNull();
208
+ });
209
+
210
+ it("should serialize identities without leaking secrets", () => {
211
+ expect(serializeIdentity({ kind: "oauth", email: "alice@example.com" })).toBe("oauth:alice@example.com");
212
+ expect(serializeIdentity({ kind: "cc", source: "cc-keychain", label: "label1" })).toBe(
213
+ "cc:cc-keychain:label1",
214
+ );
215
+ expect(serializeIdentity({ kind: "legacy", refreshToken: "secret-refresh-token" })).toBe("legacy:redacted");
216
+ });
211
217
  });
212
- });
213
218
  });
@@ -5,104 +5,106 @@ import type { AccountMetadata } from "./storage.js";
5
5
  type CCAccountSource = "cc-keychain" | "cc-file";
6
6
 
7
7
  export type AccountIdentity =
8
- | { kind: "oauth"; email: string }
9
- | { kind: "cc"; source: CCAccountSource; label: string }
10
- | { kind: "legacy"; refreshToken: string };
8
+ | { kind: "oauth"; email: string }
9
+ | { kind: "cc"; source: CCAccountSource; label: string }
10
+ | { kind: "legacy"; refreshToken: string };
11
11
 
12
12
  type IdentityAccount = ManagedAccount | AccountMetadata;
13
13
 
14
14
  function isCCAccountSource(source: IdentityAccount["source"]): source is CCAccountSource {
15
- return source === "cc-keychain" || source === "cc-file";
15
+ return source === "cc-keychain" || source === "cc-file";
16
16
  }
17
17
 
18
18
  function isAccountIdentity(value: unknown): value is AccountIdentity {
19
- if (!value || typeof value !== "object") return false;
20
-
21
- const candidate = value as Record<string, unknown>;
22
- switch (candidate.kind) {
23
- case "oauth":
24
- return typeof candidate.email === "string" && candidate.email.length > 0;
25
- case "cc":
26
- return isCCAccountSource(candidate.source as IdentityAccount["source"]) && typeof candidate.label === "string";
27
- case "legacy":
28
- return typeof candidate.refreshToken === "string" && candidate.refreshToken.length > 0;
29
- default:
30
- return false;
31
- }
19
+ if (!value || typeof value !== "object") return false;
20
+
21
+ const candidate = value as Record<string, unknown>;
22
+ switch (candidate.kind) {
23
+ case "oauth":
24
+ return typeof candidate.email === "string" && candidate.email.length > 0;
25
+ case "cc":
26
+ return (
27
+ isCCAccountSource(candidate.source as IdentityAccount["source"]) && typeof candidate.label === "string"
28
+ );
29
+ case "legacy":
30
+ return typeof candidate.refreshToken === "string" && candidate.refreshToken.length > 0;
31
+ default:
32
+ return false;
33
+ }
32
34
  }
33
35
 
34
36
  export function resolveIdentity(account: IdentityAccount): AccountIdentity {
35
- if (isAccountIdentity(account.identity)) {
36
- return account.identity;
37
- }
37
+ if (isAccountIdentity(account.identity)) {
38
+ return account.identity;
39
+ }
38
40
 
39
- if (account.source === "oauth" && account.email) {
40
- return { kind: "oauth", email: account.email };
41
- }
41
+ if (account.source === "oauth" && account.email) {
42
+ return { kind: "oauth", email: account.email };
43
+ }
42
44
 
43
- if (isCCAccountSource(account.source) && account.label) {
44
- return { kind: "cc", source: account.source, label: account.label };
45
- }
45
+ if (isCCAccountSource(account.source) && account.label) {
46
+ return { kind: "cc", source: account.source, label: account.label };
47
+ }
46
48
 
47
- return { kind: "legacy", refreshToken: account.refreshToken };
49
+ return { kind: "legacy", refreshToken: account.refreshToken };
48
50
  }
49
51
 
50
52
  export function resolveIdentityFromCCCredential(cred: CCCredential): AccountIdentity {
51
- return {
52
- kind: "cc",
53
- source: cred.source,
54
- label: cred.label,
55
- };
53
+ return {
54
+ kind: "cc",
55
+ source: cred.source,
56
+ label: cred.label,
57
+ };
56
58
  }
57
59
 
58
60
  export function resolveIdentityFromOAuthExchange(result: { email?: string; refresh: string }): AccountIdentity {
59
- if (result.email) {
61
+ if (result.email) {
62
+ return {
63
+ kind: "oauth",
64
+ email: result.email,
65
+ };
66
+ }
67
+
60
68
  return {
61
- kind: "oauth",
62
- email: result.email,
69
+ kind: "legacy",
70
+ refreshToken: result.refresh,
63
71
  };
64
- }
65
-
66
- return {
67
- kind: "legacy",
68
- refreshToken: result.refresh,
69
- };
70
72
  }
71
73
 
72
74
  export function identitiesMatch(a: AccountIdentity, b: AccountIdentity): boolean {
73
- if (a.kind !== b.kind) return false;
74
-
75
- switch (a.kind) {
76
- case "oauth": {
77
- return a.email === (b as Extract<AccountIdentity, { kind: "oauth" }>).email;
75
+ if (a.kind !== b.kind) return false;
76
+
77
+ switch (a.kind) {
78
+ case "oauth": {
79
+ return a.email === (b as Extract<AccountIdentity, { kind: "oauth" }>).email;
80
+ }
81
+ case "cc": {
82
+ const ccIdentity = b as Extract<AccountIdentity, { kind: "cc" }>;
83
+ return a.source === ccIdentity.source && a.label === ccIdentity.label;
84
+ }
85
+ case "legacy": {
86
+ return a.refreshToken === (b as Extract<AccountIdentity, { kind: "legacy" }>).refreshToken;
87
+ }
78
88
  }
79
- case "cc": {
80
- const ccIdentity = b as Extract<AccountIdentity, { kind: "cc" }>;
81
- return a.source === ccIdentity.source && a.label === ccIdentity.label;
82
- }
83
- case "legacy": {
84
- return a.refreshToken === (b as Extract<AccountIdentity, { kind: "legacy" }>).refreshToken;
85
- }
86
- }
87
89
  }
88
90
 
89
91
  export function findByIdentity<T extends IdentityAccount>(accounts: T[], id: AccountIdentity): T | null {
90
- for (const account of accounts) {
91
- if (identitiesMatch(resolveIdentity(account), id)) {
92
- return account;
92
+ for (const account of accounts) {
93
+ if (identitiesMatch(resolveIdentity(account), id)) {
94
+ return account;
95
+ }
93
96
  }
94
- }
95
97
 
96
- return null;
98
+ return null;
97
99
  }
98
100
 
99
101
  export function serializeIdentity(id: AccountIdentity): string {
100
- switch (id.kind) {
101
- case "oauth":
102
- return `oauth:${id.email}`;
103
- case "cc":
104
- return `cc:${id.source}:${id.label}`;
105
- case "legacy":
106
- return "legacy:redacted";
107
- }
102
+ switch (id.kind) {
103
+ case "oauth":
104
+ return `oauth:${id.email}`;
105
+ case "cc":
106
+ return `cc:${id.source}:${id.label}`;
107
+ case "legacy":
108
+ return "legacy:redacted";
109
+ }
108
110
  }