opencode-multi-account-core 0.2.32 → 0.2.34

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 CHANGED
@@ -1,56 +1,56 @@
1
- # multi-account-core
2
-
3
- Shared core logic for multi-account OpenCode plugins. This package contains ~70% of the logic used by both [`anthropic-multi-account`](../anthropic-multi-account) and [`codex-multi-account`](../codex-multi-account).
4
-
5
- ## What's inside
6
-
7
- | Module | What it does |
8
- |:-------|:-------------|
9
- | AccountStore | Single write path. Serializes all disk mutations through file locking. |
10
- | AccountManager | In-memory account cache and selection strategies (sticky, round-robin, hybrid). Created via `createAccountManagerForProvider`. |
11
- | Executor | Retry loop with account rotation on auth and rate-limit failures. Created via `createExecutorForProvider`. |
12
- | Claims | Cross-process coordination via claim files with zombie detection. |
13
- | Storage | Atomic file read/write with `proper-lockfile`. |
14
- | RateLimit | Per-account rate-limit tracking with configurable backoff. Created via `createRateLimitTrackerForProvider`. |
15
- | ProactiveRefreshQueue | Background token refresh before expiry. Created via `createProactiveRefreshForProvider`. |
16
- | Config | Plugin configuration loading and validation with valibot. Created via `createConfigLoaderForProvider`. |
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). |
19
- | UI | Terminal UI primitives (ANSI formatting, confirm dialogs, select menus). |
20
- | Utils | Config directory resolution, formatting helpers. |
21
-
22
- ## Usage
23
-
24
- This package is not intended to be used directly. It is a dependency of the provider-specific plugin packages. Each module exposes a factory function that accepts provider-specific config (endpoints, client IDs, plan labels) and returns a ready-to-use instance.
25
-
26
- ```ts
27
- import { createAccountManagerForProvider } from "opencode-multi-account-core";
28
-
29
- export const AccountManager = createAccountManagerForProvider({
30
- refreshTokenFn: myRefreshToken,
31
- isTokenExpiredFn: myIsTokenExpired,
32
- });
33
- ```
1
+ # opencode-multi-account-core
2
+
3
+ Shared core for kyoli OpenCode Plugin Mode.
4
+
5
+ This package is not a user-facing plugin. It is used by:
6
+
7
+ - [`opencode-codex-multi-account`](../codex-multi-account)
8
+ - [`opencode-anthropic-multi-account`](../anthropic-multi-account)
9
+
10
+ The npm package name remains `opencode-multi-account-core` for compatibility.
11
+
12
+ ## What lives here
34
13
 
35
- ## Architecture
14
+ | Module | Purpose |
15
+ |---|---|
16
+ | `AccountStore` | File-locked account JSON storage |
17
+ | `AccountManager` | Account cache, selection, state mutation |
18
+ | `Executor` | Retry loop and account rotation |
19
+ | `Claims` | Cross-process account claims |
20
+ | `ProactiveRefreshQueue` | Background token refresh |
21
+ | `NativePluginLifecycle` | OpenCode loader/runtime/refresh wiring |
22
+ | `NativePluginAuth` | Shared OAuth method builder |
23
+ | `NativePluginLoader` | Shared `getAuth -> lifecycle.load -> hooks` flow |
24
+ | `NativePluginBootstrapAuth` | Stored-account to OpenCode `auth.json` sync helper |
36
25
 
26
+ Provider wire behavior stays in the provider packages. Server Mode behavior stays in
27
+ `@kyoli-gam/core`, `@kyoli-gam/gateway`, and `@kyoli-gam/cli`.
28
+
29
+ ## Safety
30
+
31
+ - Disk writes go through file locks.
32
+ - Writes are atomic temp-file-and-rename operations.
33
+ - Concurrent refreshes for the same account are deduplicated.
34
+ - Claim-file writes are serialized.
35
+ - Accounts are only auto-disabled when another usable account remains.
36
+ - Dead process claims are released automatically.
37
+
38
+ ## Checks
39
+
40
+ ```bash
41
+ pnpm --filter opencode-multi-account-core test:contract:native
42
+ pnpm --filter opencode-multi-account-core typecheck
43
+ pnpm --filter opencode-multi-account-core test
37
44
  ```
38
- ┌─────────────────────────────────────────────────┐
39
- │ anthropic-multi-account / codex-multi-account │
40
- │ (provider-specific: auth, usage, transforms) │
41
- ├─────────────────────────────────────────────────┤
42
- │ multi-account-core ← you are here │
43
- │ AccountStore . AccountManager . Executor │
44
- │ Claims . Storage . RateLimit . ProactiveRefresh │
45
- │ AuthMigration . Config . Utils . UI . Adapters │
46
- │ (endpoints, client IDs, plan labels) │
47
- └─────────────────────────────────────────────────┘
45
+
46
+ Root no-live plugin gate:
47
+
48
+ ```bash
49
+ pnpm run test:contract:native
48
50
  ```
49
51
 
50
- ## Safety guarantees
52
+ ## Docs
51
53
 
52
- - All disk mutations go through AccountStore with file locking
53
- - Atomic writes via temp-file-then-rename
54
- - Concurrent token refresh requests for the same account are deduplicated
55
- - Circuit breaker: an account is only auto-disabled when at least one other remains usable
56
- - Dead process claims are automatically released
54
+ - [OpenCode Plugin Mode](../../docs/opencode-plugin-mode.md)
55
+ - [OpenCode Plugin Usage](../../docs/opencode-plugin-usage.md)
56
+ - [OpenCode Plugin Core Redesign Notes](../../docs/opencode-plugin-core-redesign.md)
package/dist/index.d.ts CHANGED
@@ -46,11 +46,15 @@ declare const CredentialRefreshPatchSchema: v.ObjectSchema<{
46
46
  readonly refreshToken: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
47
47
  readonly uuid: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
48
48
  readonly accountId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
49
+ readonly accountUuid: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
50
+ readonly deviceId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
49
51
  readonly email: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
50
52
  }, undefined>;
51
53
  declare const StoredAccountSchema: v.ObjectSchema<{
52
54
  readonly uuid: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
53
55
  readonly accountId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
56
+ readonly accountUuid: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
57
+ readonly deviceId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
54
58
  readonly label: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
55
59
  readonly email: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
56
60
  readonly planTier: v.OptionalSchema<v.StringSchema<undefined>, "">;
@@ -85,6 +89,8 @@ declare const AccountStorageSchema: v.ObjectSchema<{
85
89
  readonly accounts: v.OptionalSchema<v.ArraySchema<v.ObjectSchema<{
86
90
  readonly uuid: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
87
91
  readonly accountId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
92
+ readonly accountUuid: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
93
+ readonly deviceId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
88
94
  readonly label: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
89
95
  readonly email: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
90
96
  readonly planTier: v.OptionalSchema<v.StringSchema<undefined>, "">;
@@ -139,6 +145,7 @@ type StoredAccount = v.InferOutput<typeof StoredAccountSchema>;
139
145
  type AccountStorage = v.InferOutput<typeof AccountStorageSchema>;
140
146
  type AccountSelectionStrategy = v.InferOutput<typeof AccountSelectionStrategySchema>;
141
147
  type PluginConfig = v.InferOutput<typeof PluginConfigSchema>;
148
+ type AccountMetadataPatch = Partial<Pick<StoredAccount, "accountId" | "accountUuid" | "deviceId" | "email" | "label" | "planTier">>;
142
149
  type TokenRefreshResult = {
143
150
  ok: true;
144
151
  patch: CredentialRefreshPatch;
@@ -157,6 +164,8 @@ interface ManagedAccount {
157
164
  index: number;
158
165
  uuid?: string;
159
166
  accountId?: string;
167
+ accountUuid?: string;
168
+ deviceId?: string;
160
169
  label?: string;
161
170
  email?: string;
162
171
  planTier?: string;
@@ -214,6 +223,8 @@ interface DiskCredentials {
214
223
  accessToken?: string;
215
224
  expiresAt?: number;
216
225
  accountId?: string;
226
+ accountUuid?: string;
227
+ deviceId?: string;
217
228
  }
218
229
  declare class AccountStore {
219
230
  private readonly storagePath;
@@ -268,9 +279,9 @@ interface AccountManagerInstance {
268
279
  validateNonActiveTokens(client: PluginClient): Promise<void>;
269
280
  removeAccount(index: number): Promise<boolean>;
270
281
  clearAllAccounts(): Promise<void>;
271
- addAccount(auth: OAuthCredentials, email?: string): Promise<void>;
282
+ addAccount(auth: OAuthCredentials, email?: string, metadata?: AccountMetadataPatch): Promise<void>;
272
283
  toggleEnabled(uuid: string): Promise<void>;
273
- replaceAccountCredentials(uuid: string, auth: OAuthCredentials): Promise<void>;
284
+ replaceAccountCredentials(uuid: string, auth: OAuthCredentials, metadata?: AccountMetadataPatch): Promise<void>;
274
285
  retryAuth(uuid: string, client: PluginClient): Promise<TokenRefreshResult>;
275
286
  }
276
287
  interface AccountManagerClass {
@@ -460,7 +471,6 @@ interface OAuthAdapter {
460
471
  id: string;
461
472
  authProviderId: string;
462
473
  modelDisplayName: string;
463
- statusToolName: string;
464
474
  authMethodLabel: string;
465
475
  serviceLogName: string;
466
476
  oauthClientId: string;
@@ -588,4 +598,130 @@ declare class CascadeStateManager {
588
598
  getSnapshot(): CascadeState | null;
589
599
  }
590
600
 
591
- export { ACCOUNTS_FILENAME, ANSI, type AccountManagerClass, type AccountManagerDependencies, type AccountManagerInstance, type AccountSelectionStrategy, AccountSelectionStrategySchema, type AccountStorage, AccountStorageSchema, AccountStore, type BuildFailoverPlanOptions, type CascadeState, CascadeStateManager, type ChainConfig, ChainConfigSchema, type ChainEntryConfig, ChainEntryConfigSchema, type ClaimsManager, type ClaimsMap, type ConfigLoader, type CoreConfig, type CredentialRefreshPatch, CredentialRefreshPatchSchema, type DiskCredentials, type ExecutorAccountManager, type ExecutorDependencies, type ExecutorRuntimeFactory, type FailoverCandidate, type FailoverPlan, type FailoverSkip, type KeyAction, type ManagedAccount, type MenuItem, type OAuthAdapter, type OAuthAdapterPlanLabels, type OAuthAdapterTransformConfig, type OAuthCredentials, OAuthCredentialsSchema, type PluginClient, type PluginConfig, PluginConfigSchema, type PoolChainConfig, PoolChainConfigSchema, type PoolConfig, PoolConfigSchema, PoolManager, type ProactiveRefreshDependencies, type ProactiveRefreshQueueClass, type ProactiveRefreshQueueInstance, type ProfileData, type RateLimitAccountManager, type RateLimitDependencies, type RuntimeFactoryLike, type SelectOptions, type StoredAccount, StoredAccountSchema, TokenRefreshError, type TokenRefreshResult, type UsageLimitEntry, UsageLimitEntrySchema, type UsageLimits, UsageLimitsSchema, anthropicOAuthAdapter, confirm, createAccountManagerForProvider, createClaimsManager, createConfigLoader, createExecutorForProvider, createMinimalClient, createProactiveRefreshQueueForProvider, createRateLimitHandlers, debugLog, deduplicateAccounts, formatWaitTime, getAccountLabel, getClearedOAuthBody, getConfig, getConfigDir, getErrorCode, initCoreConfig, isClaimedByOther, isTTY, isTokenRefreshError, loadAccounts, loadConfig, loadPoolChainConfig, migrateFromAuthJson, openAIOAuthAdapter, parseKey, readClaims, readStorageFromDisk, releaseClaim, resetConfigCache, savePoolChainConfig, select, setAccountsFilename, setConfigGetter, showToast, sleep, updateConfigField, withDirectoryLock, writeClaim };
601
+ type FetchLike = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
602
+ interface NativePluginManagedAccount {
603
+ uuid?: string;
604
+ isAuthDisabled?: boolean;
605
+ }
606
+ interface NativePluginStoreLike {
607
+ load(): Promise<{
608
+ accounts: unknown[];
609
+ }>;
610
+ }
611
+ interface NativePluginManagerLike<TAccount extends NativePluginManagedAccount = NativePluginManagedAccount> {
612
+ getAccountCount(): number;
613
+ getAccounts(): TAccount[];
614
+ getActiveAccount?(): TAccount | null;
615
+ setRuntimeFactory(factory: NativePluginRuntimeFactoryLike): void;
616
+ validateNonActiveTokens?(client: PluginClient): Promise<void>;
617
+ }
618
+ interface NativePluginManagerClass<TStore extends NativePluginStoreLike, TManager extends NativePluginManagerLike> {
619
+ create(store: TStore, currentAuth: OAuthCredentials, client?: PluginClient): Promise<TManager>;
620
+ }
621
+ interface NativePluginRuntimeFactoryLike {
622
+ getRuntime(uuid: string): Promise<{
623
+ fetch: FetchLike;
624
+ }>;
625
+ invalidate(uuid: string): void;
626
+ }
627
+ interface NativePluginRefreshQueueLike {
628
+ start(): void;
629
+ stop(): Promise<void> | void;
630
+ }
631
+ interface NativePluginLifecycleOptions<TStore extends NativePluginStoreLike, TManager extends NativePluginManagerLike<TAccount>, TAccount extends NativePluginManagedAccount> {
632
+ store: TStore;
633
+ client: PluginClient;
634
+ managerClass: NativePluginManagerClass<TStore, TManager>;
635
+ createRuntimeFactory: (store: TStore, client: PluginClient) => NativePluginRuntimeFactoryLike;
636
+ createRefreshQueue?: (client: PluginClient, store: TStore, onInvalidate: (uuid: string) => void) => NativePluginRefreshQueueLike;
637
+ executeWithAccountRotation: (manager: TManager, runtimeFactory: NativePluginRuntimeFactoryLike, client: PluginClient, input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
638
+ migrateFromAuthJson?: (providerKey: string, store: TStore) => Promise<boolean>;
639
+ afterManagerInitialized?: (manager: TManager, runtimeFactory: NativePluginRuntimeFactoryLike) => Promise<void> | void;
640
+ afterOAuthLoad?: (credentials: OAuthCredentials, manager: TManager) => Promise<void> | void;
641
+ createFetch?: (context: {
642
+ getManager: () => TManager | null;
643
+ getRuntimeFactory: () => NativePluginRuntimeFactoryLike | null;
644
+ defaultFetch: FetchLike;
645
+ }) => FetchLike;
646
+ createLoaderExtras?: (manager: TManager | null) => Promise<Record<string, unknown>> | Record<string, unknown>;
647
+ authJsonProviderKey: string;
648
+ oauthApiKey: string;
649
+ noAccountsMessage: string;
650
+ getAccountLabel: (account: TAccount) => string;
651
+ }
652
+ interface NativePluginLoaderResult {
653
+ apiKey: string;
654
+ fetch: FetchLike;
655
+ [key: string]: unknown;
656
+ }
657
+ interface NativePluginLifecycle<TManager extends NativePluginManagerLike = NativePluginManagerLike> {
658
+ getManager(): TManager | null;
659
+ getRuntimeFactory(): NativePluginRuntimeFactoryLike | null;
660
+ load(auth: Record<string, unknown>, provider?: Record<string, unknown>): Promise<NativePluginLoaderResult>;
661
+ }
662
+ declare function createOpenCodeNativePluginLifecycle<TStore extends NativePluginStoreLike, TAccount extends NativePluginManagedAccount, TManager extends NativePluginManagerLike<TAccount>>(options: NativePluginLifecycleOptions<TStore, TManager, TAccount>): NativePluginLifecycle<TManager>;
663
+
664
+ interface OpenCodeNativeAuthMethod<TResult = unknown> {
665
+ label: string;
666
+ type: "oauth";
667
+ authorize: (...args: unknown[]) => Promise<TResult>;
668
+ }
669
+ interface OpenCodeNativeAuthMethodsOptions<TResult = unknown> {
670
+ oauthLabel: string;
671
+ authorize: (inputs?: Record<string, string>) => Promise<TResult>;
672
+ }
673
+ declare function createOpenCodeNativeAuthMethods<TResult>(options: OpenCodeNativeAuthMethodsOptions<TResult>): OpenCodeNativeAuthMethod<TResult>[];
674
+
675
+ interface OpenCodeNativeLoaderContext<TManager extends NativePluginManagerLike = NativePluginManagerLike> {
676
+ auth: Record<string, unknown>;
677
+ provider: Record<string, unknown>;
678
+ lifecycle: NativePluginLifecycle<TManager>;
679
+ manager: TManager | null;
680
+ runtimeFactory: NativePluginRuntimeFactoryLike | null;
681
+ result: NativePluginLoaderResult;
682
+ }
683
+ interface OpenCodeNativeAuthLoaderOptions<TManager extends NativePluginManagerLike = NativePluginManagerLike> {
684
+ lifecycle: NativePluginLifecycle<TManager>;
685
+ debugLog?: (message: string, extra?: Record<string, unknown>) => void;
686
+ beforeAuth?: (provider: Record<string, unknown>) => Promise<void> | void;
687
+ beforeLoad?: (context: {
688
+ auth: Record<string, unknown>;
689
+ provider: Record<string, unknown>;
690
+ lifecycle: NativePluginLifecycle<TManager>;
691
+ }) => Promise<void> | void;
692
+ afterLoad?: (context: OpenCodeNativeLoaderContext<TManager>) => Promise<NativePluginLoaderResult | void> | NativePluginLoaderResult | void;
693
+ }
694
+ declare function createOpenCodeNativeAuthLoader<TManager extends NativePluginManagerLike = NativePluginManagerLike>(options: OpenCodeNativeAuthLoaderOptions<TManager>): (getAuth: () => Promise<unknown>, provider: Record<string, unknown>) => Promise<NativePluginLoaderResult>;
695
+
696
+ interface OpenCodeNativeBootstrapAccount {
697
+ uuid?: string;
698
+ refreshToken: string;
699
+ accessToken?: string;
700
+ expiresAt?: number;
701
+ enabled?: boolean;
702
+ isAuthDisabled?: boolean;
703
+ }
704
+ interface OpenCodeNativeBootstrapStore {
705
+ load(): Promise<{
706
+ accounts: OpenCodeNativeBootstrapAccount[];
707
+ activeAccountUuid?: string;
708
+ }>;
709
+ }
710
+ interface OpenCodeNativeBootstrapAuthOptions {
711
+ client: PluginClient;
712
+ store: OpenCodeNativeBootstrapStore;
713
+ providerId: string;
714
+ }
715
+ declare function selectBootstrapAccount(accounts: OpenCodeNativeBootstrapAccount[], activeAccountUuid?: string): (OpenCodeNativeBootstrapAccount & {
716
+ refreshToken: string;
717
+ accessToken: string;
718
+ expiresAt: number;
719
+ }) | null;
720
+ declare function shouldSyncBootstrapAuth(currentAuth: OAuthCredentials | null, nextAuth: OAuthCredentials): boolean;
721
+ declare function syncOpenCodeNativeBootstrapAuth(options: OpenCodeNativeBootstrapAuthOptions): Promise<boolean>;
722
+ declare const __openCodeNativeBootstrapAuthTestUtils: {
723
+ selectBootstrapAccount: typeof selectBootstrapAccount;
724
+ shouldSyncBootstrapAuth: typeof shouldSyncBootstrapAuth;
725
+ };
726
+
727
+ export { ACCOUNTS_FILENAME, ANSI, type AccountManagerClass, type AccountManagerDependencies, type AccountManagerInstance, type AccountMetadataPatch, type AccountSelectionStrategy, AccountSelectionStrategySchema, type AccountStorage, AccountStorageSchema, AccountStore, type BuildFailoverPlanOptions, type CascadeState, CascadeStateManager, type ChainConfig, ChainConfigSchema, type ChainEntryConfig, ChainEntryConfigSchema, type ClaimsManager, type ClaimsMap, type ConfigLoader, type CoreConfig, type CredentialRefreshPatch, CredentialRefreshPatchSchema, type DiskCredentials, type ExecutorAccountManager, type ExecutorDependencies, type ExecutorRuntimeFactory, type FailoverCandidate, type FailoverPlan, type FailoverSkip, type KeyAction, type ManagedAccount, type MenuItem, type NativePluginLifecycle, type NativePluginLifecycleOptions, type NativePluginLoaderResult, type NativePluginManagedAccount, type NativePluginManagerClass, type NativePluginManagerLike, type NativePluginRefreshQueueLike, type NativePluginRuntimeFactoryLike, type NativePluginStoreLike, type OAuthAdapter, type OAuthAdapterPlanLabels, type OAuthAdapterTransformConfig, type OAuthCredentials, OAuthCredentialsSchema, type OpenCodeNativeAuthLoaderOptions, type OpenCodeNativeAuthMethod, type OpenCodeNativeAuthMethodsOptions, type OpenCodeNativeBootstrapAccount, type OpenCodeNativeBootstrapAuthOptions, type OpenCodeNativeBootstrapStore, type OpenCodeNativeLoaderContext, type PluginClient, type PluginConfig, PluginConfigSchema, type PoolChainConfig, PoolChainConfigSchema, type PoolConfig, PoolConfigSchema, PoolManager, type ProactiveRefreshDependencies, type ProactiveRefreshQueueClass, type ProactiveRefreshQueueInstance, type ProfileData, type RateLimitAccountManager, type RateLimitDependencies, type RuntimeFactoryLike, type SelectOptions, type StoredAccount, StoredAccountSchema, TokenRefreshError, type TokenRefreshResult, type UsageLimitEntry, UsageLimitEntrySchema, type UsageLimits, UsageLimitsSchema, __openCodeNativeBootstrapAuthTestUtils, anthropicOAuthAdapter, confirm, createAccountManagerForProvider, createClaimsManager, createConfigLoader, createExecutorForProvider, createMinimalClient, createOpenCodeNativeAuthLoader, createOpenCodeNativeAuthMethods, createOpenCodeNativePluginLifecycle, createProactiveRefreshQueueForProvider, createRateLimitHandlers, debugLog, deduplicateAccounts, formatWaitTime, getAccountLabel, getClearedOAuthBody, getConfig, getConfigDir, getErrorCode, initCoreConfig, isClaimedByOther, isTTY, isTokenRefreshError, loadAccounts, loadConfig, loadPoolChainConfig, migrateFromAuthJson, openAIOAuthAdapter, parseKey, readClaims, readStorageFromDisk, releaseClaim, resetConfigCache, savePoolChainConfig, select, setAccountsFilename, setConfigGetter, showToast, sleep, syncOpenCodeNativeBootstrapAuth, updateConfigField, withDirectoryLock, writeClaim };