1import * as Schema from "effect/Schema";
2import { ProviderInstanceId } from "./providerInstance.ts";
3
4export const SharedAccountId = Schema.String.check(Schema.isPattern(/^[a-zA-Z0-9_-]{1,48}$/u));
5
6export const SharedCodexCredentials = Schema.Struct({
7 driver: Schema.Literal("codex"),
8 accessToken: Schema.NonEmptyString,
9 refreshToken: Schema.NonEmptyString,
10 idToken: Schema.NonEmptyString,
11 accountId: Schema.NonEmptyString,
12 expiresAt: Schema.Finite,
13});
14
15export const SharedClaudeCredentials = Schema.Struct({
16 driver: Schema.Literal("claudeAgent"),
17 accessToken: Schema.NonEmptyString,
18});
19
20export const SharedAccountCredentials = Schema.Union([
21 SharedCodexCredentials,
22 SharedClaudeCredentials,
23]);
24export type SharedAccountCredentials = typeof SharedAccountCredentials.Type;
25
26export const SharedAccount = Schema.Struct({
27 id: SharedAccountId,
28 label: Schema.NonEmptyString.check(Schema.isMaxLength(120)),
29 driver: Schema.Literals(["codex", "claudeAgent"]),
30 revision: Schema.Int.check(Schema.isGreaterThan(0)),
31});
32export type SharedAccount = typeof SharedAccount.Type;
33
34export const SharedAccountAccess = Schema.Union([
35 SharedCodexCredentials.mapFields(({ driver, accessToken, idToken, accountId, expiresAt }) => ({
36 driver,
37 accessToken,
38 idToken,
39 accountId,
40 expiresAt,
41 })),
42 SharedClaudeCredentials,
43]);
44export type SharedAccountAccess = typeof SharedAccountAccess.Type;
45
46export const SharedAccountCommit = Schema.Struct({
47 type: Schema.Literal("commit"),
48 id: SharedAccountId,
49 revision: Schema.Int,
50 leaseToken: Schema.NonEmptyString,
51 credentials: SharedCodexCredentials,
52});
53
54export const SharedAccountRequest = Schema.Union([
55 Schema.Struct({ type: Schema.Literal("list") }),
56 Schema.Struct({
57 type: Schema.Literal("import"),
58 label: SharedAccount.fields.label,
59 replaceId: Schema.optionalKey(SharedAccountId),
60 credentials: SharedAccountCredentials,
61 }),
62 Schema.Struct({ type: Schema.Literal("remove"), id: SharedAccountId }),
63 Schema.Struct({
64 type: Schema.Literal("acquire"),
65 id: SharedAccountId,
66 refreshRevision: Schema.optionalKey(Schema.Int),
67 }),
68 SharedAccountCommit,
69]);
70export type SharedAccountRequest = typeof SharedAccountRequest.Type;
71
72export const SharedAccountResponse = Schema.Union([
73 Schema.Struct({ type: Schema.Literal("list"), accounts: Schema.Array(SharedAccount) }),
74 Schema.Struct({ type: Schema.Literal("imported"), account: SharedAccount }),
75 Schema.Struct({ type: Schema.Literal("removed") }),
76 Schema.Struct({
77 type: Schema.Literal("access"),
78 account: SharedAccount,
79 credentials: SharedAccountAccess,
80 }),
81 Schema.Struct({
82 type: Schema.Literal("refresh"),
83 account: SharedAccount,
84 leaseToken: Schema.NonEmptyString,
85 credentials: SharedCodexCredentials,
86 expiresAt: Schema.Finite,
87 }),
88 Schema.Struct({ type: Schema.Literal("busy"), retryAt: Schema.Finite }),
89]);
90export type SharedAccountResponse = typeof SharedAccountResponse.Type;
91
92export const SharedProfilesRequest = Schema.Union([
93 Schema.Struct({ type: Schema.Literal("status") }),
94 Schema.Struct({ type: Schema.Literal("sync") }),
95 Schema.Struct({
96 type: Schema.Literal("connect"),
97 url: Schema.NonEmptyString,
98 tokenFile: Schema.NonEmptyString,
99 }),
100 Schema.Struct({ type: Schema.Literal("disconnect") }),
101 Schema.Struct({
102 type: Schema.Literal("import"),
103 instanceId: ProviderInstanceId,
104 tokenFile: Schema.optionalKey(Schema.NonEmptyString),
105 replaceId: Schema.optionalKey(SharedAccountId),
106 }),
107 Schema.Struct({ type: Schema.Literal("remove"), id: SharedAccountId }),
108]);
109export type SharedProfilesRequest = typeof SharedProfilesRequest.Type;
110
111export const SharedProfilesStatus = Schema.Struct({
112 connected: Schema.Boolean,
113 accounts: Schema.Array(SharedAccount),
114});
115export type SharedProfilesStatus = typeof SharedProfilesStatus.Type;
116
117export class SharedAccountError extends Schema.TaggedError<SharedAccountError>()(
118 "SharedAccountError",
119 {
120 reason: Schema.Literals([
121 "storage",
122 "missing",
123 "stale",
124 "identity",
125 "connection",
126 "refresh",
127 "busy",
128 "setup",
129 ]),
130 cause: Schema.optionalKey(Schema.Redacted(Schema.Defect())),
131 },
132) {
133 override get message(): string {
134 switch (this.reason) {
135 case "setup":
136 return "This profile could not be shared. Use a signed-in Codex profile or provide a Claude setup-token file.";
137 case "busy":
138 return "Another host is refreshing this account. Retry in a moment.";
139 case "missing":
140 return "This shared account was removed. Choose another account in Providers.";
141 case "stale":
142 return "Another host updated this account. Reconnect to load its latest credentials.";
143 case "identity":
144 return "The refreshed account changed. Import the intended account again.";
145 case "connection":
146 return "The shared server is unavailable. Reconnect before refreshing this account.";
147 case "refresh":
148 return "Codex could not refresh this account. Reconnect the account on the shared server.";
149 case "storage":
150 return "Shared accounts could not be saved. Check the shared server’s storage and retry.";
151 }
152 }
153}