opencode-multi-account-core 0.1.1 → 0.2.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.
- package/README.md +3 -4
- package/dist/account-manager.d.ts +47 -0
- package/dist/account-store.d.ts +17 -0
- package/dist/adapters/anthropic.d.ts +2 -0
- package/dist/adapters/index.d.ts +3 -0
- package/dist/adapters/openai.d.ts +2 -0
- package/dist/adapters/types.d.ts +28 -0
- package/dist/auth-migration.d.ts +12 -0
- package/dist/claims.d.ts +8 -0
- package/dist/config.d.ts +8 -0
- package/dist/constants.d.ts +2 -0
- package/dist/executor.d.ts +27 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +66 -0
- package/dist/proactive-refresh.d.ts +16 -0
- package/dist/rate-limit.d.ts +25 -0
- package/dist/storage.d.ts +4 -0
- package/dist/types.d.ts +187 -0
- package/dist/ui/ansi.d.ts +17 -0
- package/dist/ui/confirm.d.ts +1 -0
- package/dist/ui/select.d.ts +13 -0
- package/dist/utils.d.ts +9 -0
- package/package.json +13 -14
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ Shared core logic for multi-account OpenCode plugins. This package contains ~70%
|
|
|
15
15
|
| ProactiveRefreshQueue | Background token refresh before expiry. Created via `createProactiveRefreshForProvider`. |
|
|
16
16
|
| Config | Plugin configuration loading and validation with valibot. Created via `createConfigLoaderForProvider`. |
|
|
17
17
|
| AuthMigration | One-time import of existing single-account OAuth creds from OpenCode's `auth.json`. |
|
|
18
|
+
| Adapters | Provider-specific OAuth adapter definitions (endpoints, client IDs, plan labels). |
|
|
18
19
|
| UI | Terminal UI primitives (ANSI formatting, confirm dialogs, select menus). |
|
|
19
20
|
| Utils | Config directory resolution, formatting helpers. |
|
|
20
21
|
|
|
@@ -41,10 +42,8 @@ export const AccountManager = createAccountManagerForProvider({
|
|
|
41
42
|
│ multi-account-core ← you are here │
|
|
42
43
|
│ AccountStore . AccountManager . Executor │
|
|
43
44
|
│ Claims . Storage . RateLimit . ProactiveRefresh │
|
|
44
|
-
│ AuthMigration . Config . Utils . UI
|
|
45
|
-
|
|
46
|
-
│ oauth-adapters │
|
|
47
|
-
│ (endpoints, client IDs, plan labels) │
|
|
45
|
+
│ AuthMigration . Config . Utils . UI . Adapters │
|
|
46
|
+
│ (endpoints, client IDs, plan labels) │
|
|
48
47
|
└─────────────────────────────────────────────────┘
|
|
49
48
|
```
|
|
50
49
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { AccountStore } from "./account-store";
|
|
2
|
+
import type { ManagedAccount, OAuthCredentials, PluginClient, TokenRefreshResult, UsageLimits } from "./types";
|
|
3
|
+
export interface ProfileData {
|
|
4
|
+
email?: string;
|
|
5
|
+
planTier: string;
|
|
6
|
+
}
|
|
7
|
+
export interface RuntimeFactoryLike {
|
|
8
|
+
invalidate(uuid: string): void;
|
|
9
|
+
}
|
|
10
|
+
export interface AccountManagerDependencies {
|
|
11
|
+
providerAuthId: string;
|
|
12
|
+
isTokenExpired: (account: Pick<ManagedAccount, "accessToken" | "expiresAt">) => boolean;
|
|
13
|
+
refreshToken: (currentRefreshToken: string, accountId: string, client: PluginClient) => Promise<TokenRefreshResult>;
|
|
14
|
+
}
|
|
15
|
+
export interface AccountManagerInstance {
|
|
16
|
+
initialize(currentAuth: OAuthCredentials, client?: PluginClient): Promise<void>;
|
|
17
|
+
refresh(): Promise<void>;
|
|
18
|
+
getAccountCount(): number;
|
|
19
|
+
getAccounts(): ManagedAccount[];
|
|
20
|
+
getActiveAccount(): ManagedAccount | null;
|
|
21
|
+
setClient(client: PluginClient): void;
|
|
22
|
+
setRuntimeFactory(factory: RuntimeFactoryLike): void;
|
|
23
|
+
hasAnyUsableAccount(): boolean;
|
|
24
|
+
isRateLimited(account: ManagedAccount): boolean;
|
|
25
|
+
clearExpiredRateLimits(): void;
|
|
26
|
+
getMinWaitTime(): number;
|
|
27
|
+
selectAccount(): Promise<ManagedAccount | null>;
|
|
28
|
+
markRateLimited(uuid: string, backoffMs?: number): Promise<void>;
|
|
29
|
+
markRevoked(uuid: string): Promise<void>;
|
|
30
|
+
markSuccess(uuid: string): Promise<void>;
|
|
31
|
+
markAuthFailure(uuid: string, result: TokenRefreshResult): Promise<void>;
|
|
32
|
+
applyUsageCache(uuid: string, usage: UsageLimits): Promise<void>;
|
|
33
|
+
applyProfileCache(uuid: string, profile: ProfileData): Promise<void>;
|
|
34
|
+
ensureValidToken(uuid: string, client: PluginClient): Promise<TokenRefreshResult>;
|
|
35
|
+
validateNonActiveTokens(client: PluginClient): Promise<void>;
|
|
36
|
+
removeAccount(index: number): Promise<boolean>;
|
|
37
|
+
clearAllAccounts(): Promise<void>;
|
|
38
|
+
addAccount(auth: OAuthCredentials): Promise<void>;
|
|
39
|
+
toggleEnabled(uuid: string): Promise<void>;
|
|
40
|
+
replaceAccountCredentials(uuid: string, auth: OAuthCredentials): Promise<void>;
|
|
41
|
+
retryAuth(uuid: string, client: PluginClient): Promise<TokenRefreshResult>;
|
|
42
|
+
}
|
|
43
|
+
export interface AccountManagerClass {
|
|
44
|
+
new (store: AccountStore): AccountManagerInstance;
|
|
45
|
+
create(store: AccountStore, currentAuth: OAuthCredentials, client?: PluginClient): Promise<AccountManagerInstance>;
|
|
46
|
+
}
|
|
47
|
+
export declare function createAccountManagerForProvider(dependencies: AccountManagerDependencies): AccountManagerClass;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AccountStorage, StoredAccount } from "./types";
|
|
2
|
+
export interface DiskCredentials {
|
|
3
|
+
refreshToken: string;
|
|
4
|
+
accessToken?: string;
|
|
5
|
+
expiresAt?: number;
|
|
6
|
+
accountId?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class AccountStore {
|
|
9
|
+
load(): Promise<AccountStorage>;
|
|
10
|
+
readCredentials(uuid: string): Promise<DiskCredentials | null>;
|
|
11
|
+
mutateAccount(uuid: string, fn: (account: StoredAccount) => void): Promise<StoredAccount | null>;
|
|
12
|
+
mutateStorage(fn: (storage: AccountStorage) => void): Promise<void>;
|
|
13
|
+
addAccount(account: StoredAccount): Promise<void>;
|
|
14
|
+
removeAccount(uuid: string): Promise<boolean>;
|
|
15
|
+
setActiveUuid(uuid: string | undefined): Promise<void>;
|
|
16
|
+
clear(): Promise<void>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface OAuthAdapterTransformConfig {
|
|
2
|
+
rewriteOpenCodeBranding: boolean;
|
|
3
|
+
addToolPrefix: boolean;
|
|
4
|
+
stripToolPrefixInResponse: boolean;
|
|
5
|
+
enableMessagesBetaQuery: boolean;
|
|
6
|
+
}
|
|
7
|
+
export type OAuthAdapterPlanLabels = Record<string, string>;
|
|
8
|
+
export interface OAuthAdapter {
|
|
9
|
+
id: string;
|
|
10
|
+
authProviderId: string;
|
|
11
|
+
modelDisplayName: string;
|
|
12
|
+
statusToolName: string;
|
|
13
|
+
authMethodLabel: string;
|
|
14
|
+
serviceLogName: string;
|
|
15
|
+
oauthClientId: string;
|
|
16
|
+
tokenEndpoint: string;
|
|
17
|
+
usageEndpoint: string;
|
|
18
|
+
profileEndpoint: string;
|
|
19
|
+
oauthBetaHeader: string;
|
|
20
|
+
requestBetaHeader: string;
|
|
21
|
+
cliUserAgent: string;
|
|
22
|
+
toolPrefix: string;
|
|
23
|
+
accountStorageFilename: string;
|
|
24
|
+
transform: OAuthAdapterTransformConfig;
|
|
25
|
+
planLabels: OAuthAdapterPlanLabels;
|
|
26
|
+
supported: boolean;
|
|
27
|
+
unsupportedReason?: string;
|
|
28
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AccountStore } from "./account-store";
|
|
2
|
+
/**
|
|
3
|
+
* Imports an existing OAuth credential from OpenCode's auth.json
|
|
4
|
+
* into the multi-account storage on first use.
|
|
5
|
+
*
|
|
6
|
+
* Only runs when storage has zero accounts. Does not modify auth.json.
|
|
7
|
+
*
|
|
8
|
+
* @param providerKey - The key in auth.json ("anthropic" or "openai")
|
|
9
|
+
* @param store - The AccountStore instance to import into
|
|
10
|
+
* @returns true if a credential was imported, false otherwise
|
|
11
|
+
*/
|
|
12
|
+
export declare function migrateFromAuthJson(providerKey: string, store: AccountStore): Promise<boolean>;
|
package/dist/claims.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type ClaimsMap = Record<string, {
|
|
2
|
+
pid: number;
|
|
3
|
+
at: number;
|
|
4
|
+
}>;
|
|
5
|
+
export declare function readClaims(): Promise<ClaimsMap>;
|
|
6
|
+
export declare function writeClaim(accountId: string): Promise<void>;
|
|
7
|
+
export declare function releaseClaim(accountId: string): Promise<void>;
|
|
8
|
+
export declare function isClaimedByOther(claims: ClaimsMap, accountId: string | undefined): boolean;
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PluginConfig } from "./types";
|
|
2
|
+
export type CoreConfig = Pick<PluginConfig, "quiet_mode" | "debug">;
|
|
3
|
+
export declare function initCoreConfig(filename: string): void;
|
|
4
|
+
export declare function loadConfig(): Promise<PluginConfig>;
|
|
5
|
+
export declare function getConfig(): PluginConfig;
|
|
6
|
+
export declare function resetConfigCache(): void;
|
|
7
|
+
export declare function setConfigGetter(getter: () => PluginConfig): void;
|
|
8
|
+
export declare function updateConfigField<K extends keyof PluginConfig>(key: K, value: PluginConfig[K]): Promise<void>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ManagedAccount, PluginClient, TokenRefreshResult } from "./types";
|
|
2
|
+
export interface ExecutorAccountManager {
|
|
3
|
+
getAccountCount(): number;
|
|
4
|
+
refresh(): Promise<void>;
|
|
5
|
+
selectAccount(): Promise<ManagedAccount | null>;
|
|
6
|
+
markSuccess(uuid: string): Promise<void>;
|
|
7
|
+
markAuthFailure(uuid: string, result: TokenRefreshResult): Promise<void>;
|
|
8
|
+
markRevoked(uuid: string): Promise<void>;
|
|
9
|
+
hasAnyUsableAccount(): boolean;
|
|
10
|
+
getMinWaitTime(): number;
|
|
11
|
+
}
|
|
12
|
+
export interface ExecutorRuntimeFactory {
|
|
13
|
+
getRuntime(uuid: string): Promise<{
|
|
14
|
+
fetch: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
15
|
+
}>;
|
|
16
|
+
invalidate(uuid: string): void;
|
|
17
|
+
}
|
|
18
|
+
export interface ExecutorDependencies {
|
|
19
|
+
handleRateLimitResponse: (manager: unknown, client: PluginClient, account: ManagedAccount, response: Response) => Promise<void>;
|
|
20
|
+
formatWaitTime: (ms: number) => string;
|
|
21
|
+
sleep: (ms: number) => Promise<void>;
|
|
22
|
+
showToast: (client: PluginClient, message: string, variant: "info" | "warning" | "success" | "error") => Promise<void>;
|
|
23
|
+
getAccountLabel: (account: ManagedAccount) => string;
|
|
24
|
+
}
|
|
25
|
+
export declare function createExecutorForProvider(providerName: string, dependencies: ExecutorDependencies): {
|
|
26
|
+
executeWithAccountRotation: (manager: ExecutorAccountManager, runtimeFactory: ExecutorRuntimeFactory, client: PluginClient, input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
27
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export * from "./account-manager";
|
|
2
|
+
export * from "./account-store";
|
|
3
|
+
export * from "./claims";
|
|
4
|
+
export * from "./config";
|
|
5
|
+
export * from "./constants";
|
|
6
|
+
export * from "./executor";
|
|
7
|
+
export * from "./proactive-refresh";
|
|
8
|
+
export * from "./rate-limit";
|
|
9
|
+
export * from "./storage";
|
|
10
|
+
export * from "./types";
|
|
11
|
+
export * from "./utils";
|
|
12
|
+
export * from "./auth-migration";
|
|
13
|
+
export * from "./ui/ansi";
|
|
14
|
+
export * from "./ui/confirm";
|
|
15
|
+
export * from "./ui/select";
|
|
16
|
+
export * from "./adapters";
|
package/dist/index.js
CHANGED
|
@@ -1752,6 +1752,70 @@ async function confirm(message, defaultYes = false) {
|
|
|
1752
1752
|
const result = await select(items, { message });
|
|
1753
1753
|
return result ?? false;
|
|
1754
1754
|
}
|
|
1755
|
+
|
|
1756
|
+
// src/adapters/anthropic.ts
|
|
1757
|
+
var anthropicOAuthAdapter = {
|
|
1758
|
+
id: "anthropic",
|
|
1759
|
+
authProviderId: "anthropic",
|
|
1760
|
+
modelDisplayName: "Claude",
|
|
1761
|
+
statusToolName: "claude_multiauth_status",
|
|
1762
|
+
authMethodLabel: "Claude Pro/Max (Multi-Auth)",
|
|
1763
|
+
serviceLogName: "claude-multiauth",
|
|
1764
|
+
oauthClientId: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
|
|
1765
|
+
tokenEndpoint: "https://console.anthropic.com/v1/oauth/token",
|
|
1766
|
+
usageEndpoint: "https://api.anthropic.com/api/oauth/usage",
|
|
1767
|
+
profileEndpoint: "https://api.anthropic.com/api/oauth/profile",
|
|
1768
|
+
oauthBetaHeader: "oauth-2025-04-20",
|
|
1769
|
+
requestBetaHeader: "oauth-2025-04-20,interleaved-thinking-2025-05-14",
|
|
1770
|
+
cliUserAgent: "claude-cli/2.1.2 (external, cli)",
|
|
1771
|
+
toolPrefix: "mcp_",
|
|
1772
|
+
accountStorageFilename: "anthropic-multi-account-accounts.json",
|
|
1773
|
+
transform: {
|
|
1774
|
+
rewriteOpenCodeBranding: true,
|
|
1775
|
+
addToolPrefix: true,
|
|
1776
|
+
stripToolPrefixInResponse: true,
|
|
1777
|
+
enableMessagesBetaQuery: true
|
|
1778
|
+
},
|
|
1779
|
+
planLabels: {
|
|
1780
|
+
max: "Claude Max",
|
|
1781
|
+
pro: "Claude Pro",
|
|
1782
|
+
free: "Free"
|
|
1783
|
+
},
|
|
1784
|
+
supported: true
|
|
1785
|
+
};
|
|
1786
|
+
|
|
1787
|
+
// src/adapters/openai.ts
|
|
1788
|
+
var ISSUER = "https://auth.openai.com";
|
|
1789
|
+
var openAIOAuthAdapter = {
|
|
1790
|
+
id: "openai",
|
|
1791
|
+
authProviderId: "openai",
|
|
1792
|
+
modelDisplayName: "ChatGPT",
|
|
1793
|
+
statusToolName: "chatgpt_multiauth_status",
|
|
1794
|
+
authMethodLabel: "ChatGPT Plus/Pro (Multi-Auth)",
|
|
1795
|
+
serviceLogName: "chatgpt-multiauth",
|
|
1796
|
+
oauthClientId: "app_EMoamEEZ73f0CkXaXp7hrann",
|
|
1797
|
+
tokenEndpoint: `${ISSUER}/oauth/token`,
|
|
1798
|
+
usageEndpoint: "",
|
|
1799
|
+
profileEndpoint: "",
|
|
1800
|
+
oauthBetaHeader: "",
|
|
1801
|
+
requestBetaHeader: "",
|
|
1802
|
+
cliUserAgent: "opencode/1.1.53",
|
|
1803
|
+
toolPrefix: "mcp_",
|
|
1804
|
+
accountStorageFilename: "openai-multi-account-accounts.json",
|
|
1805
|
+
transform: {
|
|
1806
|
+
rewriteOpenCodeBranding: false,
|
|
1807
|
+
addToolPrefix: false,
|
|
1808
|
+
stripToolPrefixInResponse: false,
|
|
1809
|
+
enableMessagesBetaQuery: false
|
|
1810
|
+
},
|
|
1811
|
+
planLabels: {
|
|
1812
|
+
pro: "ChatGPT Pro",
|
|
1813
|
+
plus: "ChatGPT Plus",
|
|
1814
|
+
go: "ChatGPT Go",
|
|
1815
|
+
free: "Free"
|
|
1816
|
+
},
|
|
1817
|
+
supported: true
|
|
1818
|
+
};
|
|
1755
1819
|
export {
|
|
1756
1820
|
ACCOUNTS_FILENAME,
|
|
1757
1821
|
ANSI,
|
|
@@ -1764,6 +1828,7 @@ export {
|
|
|
1764
1828
|
StoredAccountSchema,
|
|
1765
1829
|
UsageLimitEntrySchema,
|
|
1766
1830
|
UsageLimitsSchema,
|
|
1831
|
+
anthropicOAuthAdapter,
|
|
1767
1832
|
confirm,
|
|
1768
1833
|
createAccountManagerForProvider,
|
|
1769
1834
|
createExecutorForProvider,
|
|
@@ -1783,6 +1848,7 @@ export {
|
|
|
1783
1848
|
loadAccounts,
|
|
1784
1849
|
loadConfig,
|
|
1785
1850
|
migrateFromAuthJson,
|
|
1851
|
+
openAIOAuthAdapter,
|
|
1786
1852
|
parseKey,
|
|
1787
1853
|
readClaims,
|
|
1788
1854
|
readStorageFromDisk,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AccountStore } from "./account-store";
|
|
2
|
+
import type { PluginClient, PluginConfig, StoredAccount, TokenRefreshResult } from "./types";
|
|
3
|
+
export interface ProactiveRefreshDependencies {
|
|
4
|
+
getConfig: () => PluginConfig;
|
|
5
|
+
refreshToken: (currentRefreshToken: string, accountId: string, client: PluginClient) => Promise<TokenRefreshResult>;
|
|
6
|
+
isTokenExpired: (account: Pick<StoredAccount, "accessToken" | "expiresAt">) => boolean;
|
|
7
|
+
debugLog: (client: PluginClient, message: string, extra?: Record<string, unknown>) => void;
|
|
8
|
+
}
|
|
9
|
+
export interface ProactiveRefreshQueueInstance {
|
|
10
|
+
start(): void;
|
|
11
|
+
stop(): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export interface ProactiveRefreshQueueClass {
|
|
14
|
+
new (client: PluginClient, store: AccountStore, onInvalidate?: (uuid: string) => void): ProactiveRefreshQueueInstance;
|
|
15
|
+
}
|
|
16
|
+
export declare function createProactiveRefreshQueueForProvider(dependencies: ProactiveRefreshDependencies): ProactiveRefreshQueueClass;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ManagedAccount, PluginClient, PluginConfig, UsageLimits } from "./types";
|
|
2
|
+
export interface RateLimitDependencies {
|
|
3
|
+
fetchUsage: (accessToken: string, accountId?: string) => Promise<{
|
|
4
|
+
ok: true;
|
|
5
|
+
data: UsageLimits;
|
|
6
|
+
} | {
|
|
7
|
+
ok: false;
|
|
8
|
+
reason: string;
|
|
9
|
+
}>;
|
|
10
|
+
getConfig: () => Pick<PluginConfig, "default_retry_after_ms">;
|
|
11
|
+
formatWaitTime: (ms: number) => string;
|
|
12
|
+
getAccountLabel: (account: ManagedAccount) => string;
|
|
13
|
+
showToast: (client: PluginClient, message: string, variant: "info" | "warning" | "success" | "error") => Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
export interface RateLimitAccountManager {
|
|
16
|
+
markRateLimited(uuid: string, backoffMs?: number): Promise<void>;
|
|
17
|
+
applyUsageCache(uuid: string, usage: UsageLimits): Promise<void>;
|
|
18
|
+
getAccountCount(): number;
|
|
19
|
+
}
|
|
20
|
+
export declare function createRateLimitHandlers(dependencies: RateLimitDependencies): {
|
|
21
|
+
retryAfterMsFromResponse: (response: Response) => number;
|
|
22
|
+
getResetMsFromUsage: (account: ManagedAccount) => number | null;
|
|
23
|
+
fetchUsageLimits: (accessToken: string, accountId?: string) => Promise<UsageLimits | null>;
|
|
24
|
+
handleRateLimitResponse: (manager: RateLimitAccountManager, client: PluginClient, account: ManagedAccount, response: Response) => Promise<void>;
|
|
25
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AccountStorage, StoredAccount } from "./types";
|
|
2
|
+
export declare function readStorageFromDisk(targetPath: string, backupOnCorrupt: boolean): Promise<AccountStorage | null>;
|
|
3
|
+
export declare function deduplicateAccounts(accounts: StoredAccount[]): StoredAccount[];
|
|
4
|
+
export declare function loadAccounts(): Promise<AccountStorage | null>;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
export declare const OAuthCredentialsSchema: v.ObjectSchema<{
|
|
3
|
+
readonly type: v.LiteralSchema<"oauth", undefined>;
|
|
4
|
+
readonly refresh: v.StringSchema<undefined>;
|
|
5
|
+
readonly access: v.StringSchema<undefined>;
|
|
6
|
+
readonly expires: v.NumberSchema<undefined>;
|
|
7
|
+
}, undefined>;
|
|
8
|
+
export declare const UsageLimitEntrySchema: v.ObjectSchema<{
|
|
9
|
+
readonly utilization: v.NumberSchema<undefined>;
|
|
10
|
+
readonly resets_at: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
11
|
+
}, undefined>;
|
|
12
|
+
export declare const UsageLimitsSchema: v.ObjectSchema<{
|
|
13
|
+
readonly five_hour: v.OptionalSchema<v.NullableSchema<v.ObjectSchema<{
|
|
14
|
+
readonly utilization: v.NumberSchema<undefined>;
|
|
15
|
+
readonly resets_at: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
16
|
+
}, undefined>, undefined>, null>;
|
|
17
|
+
readonly seven_day: v.OptionalSchema<v.NullableSchema<v.ObjectSchema<{
|
|
18
|
+
readonly utilization: v.NumberSchema<undefined>;
|
|
19
|
+
readonly resets_at: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
20
|
+
}, undefined>, undefined>, null>;
|
|
21
|
+
readonly seven_day_sonnet: v.OptionalSchema<v.NullableSchema<v.ObjectSchema<{
|
|
22
|
+
readonly utilization: v.NumberSchema<undefined>;
|
|
23
|
+
readonly resets_at: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
24
|
+
}, undefined>, undefined>, null>;
|
|
25
|
+
}, undefined>;
|
|
26
|
+
export declare const CredentialRefreshPatchSchema: v.ObjectSchema<{
|
|
27
|
+
readonly accessToken: v.StringSchema<undefined>;
|
|
28
|
+
readonly expiresAt: v.NumberSchema<undefined>;
|
|
29
|
+
readonly refreshToken: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
30
|
+
readonly uuid: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
31
|
+
readonly accountId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
32
|
+
readonly email: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
33
|
+
}, undefined>;
|
|
34
|
+
export declare const StoredAccountSchema: v.ObjectSchema<{
|
|
35
|
+
readonly uuid: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
36
|
+
readonly accountId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
37
|
+
readonly label: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
38
|
+
readonly email: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
39
|
+
readonly planTier: v.OptionalSchema<v.StringSchema<undefined>, "">;
|
|
40
|
+
readonly refreshToken: v.StringSchema<undefined>;
|
|
41
|
+
readonly accessToken: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
42
|
+
readonly expiresAt: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
|
|
43
|
+
readonly addedAt: v.NumberSchema<undefined>;
|
|
44
|
+
readonly lastUsed: v.NumberSchema<undefined>;
|
|
45
|
+
readonly enabled: v.OptionalSchema<v.BooleanSchema<undefined>, true>;
|
|
46
|
+
readonly rateLimitResetAt: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
|
|
47
|
+
readonly cachedUsage: v.OptionalSchema<v.ObjectSchema<{
|
|
48
|
+
readonly five_hour: v.OptionalSchema<v.NullableSchema<v.ObjectSchema<{
|
|
49
|
+
readonly utilization: v.NumberSchema<undefined>;
|
|
50
|
+
readonly resets_at: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
51
|
+
}, undefined>, undefined>, null>;
|
|
52
|
+
readonly seven_day: v.OptionalSchema<v.NullableSchema<v.ObjectSchema<{
|
|
53
|
+
readonly utilization: v.NumberSchema<undefined>;
|
|
54
|
+
readonly resets_at: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
55
|
+
}, undefined>, undefined>, null>;
|
|
56
|
+
readonly seven_day_sonnet: v.OptionalSchema<v.NullableSchema<v.ObjectSchema<{
|
|
57
|
+
readonly utilization: v.NumberSchema<undefined>;
|
|
58
|
+
readonly resets_at: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
59
|
+
}, undefined>, undefined>, null>;
|
|
60
|
+
}, undefined>, undefined>;
|
|
61
|
+
readonly cachedUsageAt: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
|
|
62
|
+
readonly consecutiveAuthFailures: v.OptionalSchema<v.NumberSchema<undefined>, 0>;
|
|
63
|
+
readonly isAuthDisabled: v.OptionalSchema<v.BooleanSchema<undefined>, false>;
|
|
64
|
+
readonly authDisabledReason: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
65
|
+
}, undefined>;
|
|
66
|
+
export declare const AccountStorageSchema: v.ObjectSchema<{
|
|
67
|
+
readonly version: v.LiteralSchema<1, undefined>;
|
|
68
|
+
readonly accounts: v.OptionalSchema<v.ArraySchema<v.ObjectSchema<{
|
|
69
|
+
readonly uuid: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
70
|
+
readonly accountId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
71
|
+
readonly label: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
72
|
+
readonly email: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
73
|
+
readonly planTier: v.OptionalSchema<v.StringSchema<undefined>, "">;
|
|
74
|
+
readonly refreshToken: v.StringSchema<undefined>;
|
|
75
|
+
readonly accessToken: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
76
|
+
readonly expiresAt: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
|
|
77
|
+
readonly addedAt: v.NumberSchema<undefined>;
|
|
78
|
+
readonly lastUsed: v.NumberSchema<undefined>;
|
|
79
|
+
readonly enabled: v.OptionalSchema<v.BooleanSchema<undefined>, true>;
|
|
80
|
+
readonly rateLimitResetAt: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
|
|
81
|
+
readonly cachedUsage: v.OptionalSchema<v.ObjectSchema<{
|
|
82
|
+
readonly five_hour: v.OptionalSchema<v.NullableSchema<v.ObjectSchema<{
|
|
83
|
+
readonly utilization: v.NumberSchema<undefined>;
|
|
84
|
+
readonly resets_at: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
85
|
+
}, undefined>, undefined>, null>;
|
|
86
|
+
readonly seven_day: v.OptionalSchema<v.NullableSchema<v.ObjectSchema<{
|
|
87
|
+
readonly utilization: v.NumberSchema<undefined>;
|
|
88
|
+
readonly resets_at: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
89
|
+
}, undefined>, undefined>, null>;
|
|
90
|
+
readonly seven_day_sonnet: v.OptionalSchema<v.NullableSchema<v.ObjectSchema<{
|
|
91
|
+
readonly utilization: v.NumberSchema<undefined>;
|
|
92
|
+
readonly resets_at: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
93
|
+
}, undefined>, undefined>, null>;
|
|
94
|
+
}, undefined>, undefined>;
|
|
95
|
+
readonly cachedUsageAt: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
|
|
96
|
+
readonly consecutiveAuthFailures: v.OptionalSchema<v.NumberSchema<undefined>, 0>;
|
|
97
|
+
readonly isAuthDisabled: v.OptionalSchema<v.BooleanSchema<undefined>, false>;
|
|
98
|
+
readonly authDisabledReason: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
99
|
+
}, undefined>, undefined>, readonly []>;
|
|
100
|
+
readonly activeAccountUuid: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
101
|
+
}, undefined>;
|
|
102
|
+
export declare const AccountSelectionStrategySchema: v.PicklistSchema<["sticky", "round-robin", "hybrid"], undefined>;
|
|
103
|
+
export declare const PluginConfigSchema: v.ObjectSchema<{
|
|
104
|
+
readonly account_selection_strategy: v.OptionalSchema<v.PicklistSchema<["sticky", "round-robin", "hybrid"], undefined>, "sticky">;
|
|
105
|
+
readonly cross_process_claims: v.OptionalSchema<v.BooleanSchema<undefined>, true>;
|
|
106
|
+
readonly soft_quota_threshold_percent: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 0, undefined>, v.MaxValueAction<number, 100, undefined>]>, 100>;
|
|
107
|
+
readonly rate_limit_min_backoff_ms: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 0, undefined>]>, 30000>;
|
|
108
|
+
readonly default_retry_after_ms: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 0, undefined>]>, 60000>;
|
|
109
|
+
readonly max_consecutive_auth_failures: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 1, undefined>]>, 3>;
|
|
110
|
+
readonly token_failure_backoff_ms: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 0, undefined>]>, 30000>;
|
|
111
|
+
readonly proactive_refresh: v.OptionalSchema<v.BooleanSchema<undefined>, true>;
|
|
112
|
+
readonly proactive_refresh_buffer_seconds: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 60, undefined>]>, 1800>;
|
|
113
|
+
readonly proactive_refresh_interval_seconds: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 30, undefined>]>, 300>;
|
|
114
|
+
readonly quiet_mode: v.OptionalSchema<v.BooleanSchema<undefined>, false>;
|
|
115
|
+
readonly debug: v.OptionalSchema<v.BooleanSchema<undefined>, false>;
|
|
116
|
+
}, undefined>;
|
|
117
|
+
export type OAuthCredentials = v.InferOutput<typeof OAuthCredentialsSchema>;
|
|
118
|
+
export type UsageLimitEntry = v.InferOutput<typeof UsageLimitEntrySchema>;
|
|
119
|
+
export type UsageLimits = v.InferOutput<typeof UsageLimitsSchema>;
|
|
120
|
+
export type CredentialRefreshPatch = v.InferOutput<typeof CredentialRefreshPatchSchema>;
|
|
121
|
+
export type StoredAccount = v.InferOutput<typeof StoredAccountSchema>;
|
|
122
|
+
export type AccountStorage = v.InferOutput<typeof AccountStorageSchema>;
|
|
123
|
+
export type AccountSelectionStrategy = v.InferOutput<typeof AccountSelectionStrategySchema>;
|
|
124
|
+
export type PluginConfig = v.InferOutput<typeof PluginConfigSchema>;
|
|
125
|
+
export type TokenRefreshResult = {
|
|
126
|
+
ok: true;
|
|
127
|
+
patch: CredentialRefreshPatch;
|
|
128
|
+
} | {
|
|
129
|
+
ok: false;
|
|
130
|
+
permanent: boolean;
|
|
131
|
+
status?: number;
|
|
132
|
+
};
|
|
133
|
+
export interface ManagedAccount {
|
|
134
|
+
index: number;
|
|
135
|
+
uuid?: string;
|
|
136
|
+
accountId?: string;
|
|
137
|
+
label?: string;
|
|
138
|
+
email?: string;
|
|
139
|
+
planTier?: string;
|
|
140
|
+
refreshToken: string;
|
|
141
|
+
accessToken?: string;
|
|
142
|
+
expiresAt?: number;
|
|
143
|
+
addedAt: number;
|
|
144
|
+
lastUsed: number;
|
|
145
|
+
enabled: boolean;
|
|
146
|
+
rateLimitResetAt?: number;
|
|
147
|
+
last429At?: number;
|
|
148
|
+
cachedUsage?: UsageLimits;
|
|
149
|
+
cachedUsageAt?: number;
|
|
150
|
+
consecutiveAuthFailures: number;
|
|
151
|
+
isAuthDisabled: boolean;
|
|
152
|
+
authDisabledReason?: string;
|
|
153
|
+
}
|
|
154
|
+
export interface PluginClient {
|
|
155
|
+
auth: {
|
|
156
|
+
set: (params: {
|
|
157
|
+
path: {
|
|
158
|
+
id: string;
|
|
159
|
+
};
|
|
160
|
+
body: {
|
|
161
|
+
type: string;
|
|
162
|
+
refresh: string;
|
|
163
|
+
access: string;
|
|
164
|
+
expires: number;
|
|
165
|
+
};
|
|
166
|
+
}) => Promise<void>;
|
|
167
|
+
};
|
|
168
|
+
tui: {
|
|
169
|
+
showToast: (params: {
|
|
170
|
+
body: {
|
|
171
|
+
title?: string;
|
|
172
|
+
message: string;
|
|
173
|
+
variant: "info" | "warning" | "success" | "error";
|
|
174
|
+
};
|
|
175
|
+
}) => Promise<void>;
|
|
176
|
+
};
|
|
177
|
+
app: {
|
|
178
|
+
log: (params: {
|
|
179
|
+
body: {
|
|
180
|
+
service: string;
|
|
181
|
+
level: "debug" | "info" | "warn" | "error";
|
|
182
|
+
message: string;
|
|
183
|
+
extra?: Record<string, unknown>;
|
|
184
|
+
};
|
|
185
|
+
}) => Promise<void>;
|
|
186
|
+
};
|
|
187
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const ANSI: {
|
|
2
|
+
readonly hide: "\u001B[?25l";
|
|
3
|
+
readonly show: "\u001B[?25h";
|
|
4
|
+
readonly up: (n?: number) => string;
|
|
5
|
+
readonly down: (n?: number) => string;
|
|
6
|
+
readonly clearLine: "\u001B[2K";
|
|
7
|
+
readonly cyan: "\u001B[36m";
|
|
8
|
+
readonly green: "\u001B[32m";
|
|
9
|
+
readonly red: "\u001B[31m";
|
|
10
|
+
readonly yellow: "\u001B[33m";
|
|
11
|
+
readonly dim: "\u001B[2m";
|
|
12
|
+
readonly bold: "\u001B[1m";
|
|
13
|
+
readonly reset: "\u001B[0m";
|
|
14
|
+
};
|
|
15
|
+
export type KeyAction = "up" | "down" | "enter" | "escape" | "escape-start" | null;
|
|
16
|
+
export declare function parseKey(data: Buffer): KeyAction;
|
|
17
|
+
export declare function isTTY(): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function confirm(message: string, defaultYes?: boolean): Promise<boolean>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface MenuItem<T = string> {
|
|
2
|
+
label: string;
|
|
3
|
+
value: T;
|
|
4
|
+
hint?: string;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
separator?: boolean;
|
|
7
|
+
color?: "red" | "green" | "yellow" | "cyan";
|
|
8
|
+
}
|
|
9
|
+
export interface SelectOptions {
|
|
10
|
+
message: string;
|
|
11
|
+
subtitle?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function select<T>(items: MenuItem<T>[], options: SelectOptions): Promise<T | null>;
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ManagedAccount, PluginClient } from "./types";
|
|
2
|
+
export declare function getConfigDir(): string;
|
|
3
|
+
export declare function getErrorCode(error: unknown): string | undefined;
|
|
4
|
+
export declare function formatWaitTime(ms: number): string;
|
|
5
|
+
export declare function getAccountLabel(account: ManagedAccount): string;
|
|
6
|
+
export declare function sleep(ms: number): Promise<void>;
|
|
7
|
+
export declare function showToast(client: PluginClient, message: string, variant: "info" | "warning" | "success" | "error"): Promise<void>;
|
|
8
|
+
export declare function debugLog(client: PluginClient, message: string, extra?: Record<string, unknown>): void;
|
|
9
|
+
export declare function createMinimalClient(): PluginClient;
|
package/package.json
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-multi-account-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Shared core for multi-account OpenCode plugins",
|
|
5
|
-
"
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
6
8
|
"exports": {
|
|
7
|
-
".":
|
|
9
|
+
".": {
|
|
10
|
+
"source": "./src/index.ts",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
8
15
|
},
|
|
9
16
|
"scripts": {
|
|
10
|
-
"build": "esbuild src/index.ts --bundle --outdir=dist --platform=node --format=esm --packages=external",
|
|
17
|
+
"build": "esbuild src/index.ts --bundle --outdir=dist --platform=node --format=esm --packages=external && tsc -p tsconfig.build.json --emitDeclarationOnly",
|
|
11
18
|
"typecheck": "tsc --noEmit",
|
|
12
|
-
"test": "
|
|
19
|
+
"test": "for f in tests/*.test.ts; do bun test \"$f\" || exit 1; done",
|
|
13
20
|
"prepack": "bun run build",
|
|
14
21
|
"dev": "bun run build --watch"
|
|
15
22
|
},
|
|
@@ -26,7 +33,6 @@
|
|
|
26
33
|
"directory": "packages/multi-account-core"
|
|
27
34
|
},
|
|
28
35
|
"dependencies": {
|
|
29
|
-
"opencode-oauth-adapters": "^0.1.1",
|
|
30
36
|
"proper-lockfile": "^4.1.2",
|
|
31
37
|
"valibot": "^1.2.0"
|
|
32
38
|
},
|
|
@@ -38,14 +44,7 @@
|
|
|
38
44
|
},
|
|
39
45
|
"publishConfig": {
|
|
40
46
|
"registry": "https://registry.npmjs.org",
|
|
41
|
-
"access": "public"
|
|
42
|
-
"main": "./dist/index.js",
|
|
43
|
-
"exports": {
|
|
44
|
-
".": {
|
|
45
|
-
"import": "./dist/index.js",
|
|
46
|
-
"default": "./dist/index.js"
|
|
47
|
-
}
|
|
48
|
-
}
|
|
47
|
+
"access": "public"
|
|
49
48
|
},
|
|
50
49
|
"files": [
|
|
51
50
|
"dist",
|