opencode-codex-multi-account 0.1.2 → 0.2.1
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 +0 -2
- package/dist/account-manager.d.ts +2 -0
- package/dist/account-store.d.ts +2 -0
- package/dist/auth-handler.d.ts +20 -0
- package/dist/claims.d.ts +1 -0
- package/dist/config.d.ts +2 -0
- package/dist/constants.d.ts +32 -0
- package/dist/executor.d.ts +2 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +32 -6
- package/dist/oauth.d.ts +37 -0
- package/dist/proactive-refresh.d.ts +2 -0
- package/dist/rate-limit.d.ts +2 -0
- package/dist/request-transform.d.ts +2 -0
- package/dist/runtime-factory.d.ts +18 -0
- package/dist/storage.d.ts +1 -0
- package/dist/token.d.ts +4 -0
- package/dist/types.d.ts +216 -0
- package/dist/ui/ansi.d.ts +1 -0
- package/dist/ui/auth-menu.d.ts +28 -0
- package/dist/ui/confirm.d.ts +1 -0
- package/dist/ui/select.d.ts +1 -0
- package/dist/usage.d.ts +25 -0
- package/dist/utils.d.ts +1 -0
- package/package.json +7 -9
package/README.md
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { AccountManager } from "./account-manager";
|
|
2
|
+
import type { PluginClient } from "./types";
|
|
3
|
+
type OAuthCallbackResponse = ({
|
|
4
|
+
type: "success";
|
|
5
|
+
} & {
|
|
6
|
+
refresh: string;
|
|
7
|
+
access: string;
|
|
8
|
+
expires: number;
|
|
9
|
+
accountId?: string;
|
|
10
|
+
}) | {
|
|
11
|
+
type: "failed";
|
|
12
|
+
};
|
|
13
|
+
export interface OAuthFlowResult {
|
|
14
|
+
url: string;
|
|
15
|
+
instructions: string;
|
|
16
|
+
method: "auto";
|
|
17
|
+
callback(): Promise<OAuthCallbackResponse>;
|
|
18
|
+
}
|
|
19
|
+
export declare function handleAuthorize(manager: AccountManager | null, inputs?: Record<string, string>, client?: PluginClient): Promise<OAuthFlowResult>;
|
|
20
|
+
export {};
|
package/dist/claims.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { isClaimedByOther, readClaims, releaseClaim, writeClaim, type ClaimsMap, } from "opencode-multi-account-core";
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** OpenAI OAuth adapter config */
|
|
2
|
+
export declare const OPENAI_OAUTH_ADAPTER: import("opencode-multi-account-core").OAuthAdapter;
|
|
3
|
+
/** OpenAI OAuth Client ID */
|
|
4
|
+
export declare const OPENAI_CLIENT_ID: string;
|
|
5
|
+
/** Token exchange / refresh endpoint */
|
|
6
|
+
export declare const OPENAI_TOKEN_ENDPOINT: string;
|
|
7
|
+
/** OAuth usage stats endpoint */
|
|
8
|
+
export declare const OPENAI_USAGE_ENDPOINT: string;
|
|
9
|
+
/** OAuth profile endpoint */
|
|
10
|
+
export declare const OPENAI_PROFILE_ENDPOINT: string;
|
|
11
|
+
/** Codex upstream endpoint used by OpenCode */
|
|
12
|
+
export declare const CODEX_API_ENDPOINT = "https://chatgpt.com/backend-api/codex/responses";
|
|
13
|
+
/** Codex usage/quota endpoint (WHAM API) */
|
|
14
|
+
export declare const CODEX_USAGE_ENDPOINT = "https://chatgpt.com/backend-api/wham/usage";
|
|
15
|
+
/** OpenAI OAuth issuer */
|
|
16
|
+
export declare const OAUTH_ISSUER = "https://auth.openai.com";
|
|
17
|
+
/** Local callback port for browser OAuth */
|
|
18
|
+
export declare const OAUTH_PORT = 1455;
|
|
19
|
+
/** Required beta header for OAuth requests */
|
|
20
|
+
export declare const OPENAI_BETA_HEADER: string;
|
|
21
|
+
/** User-Agent header */
|
|
22
|
+
export declare const OPENAI_CLI_USER_AGENT: string;
|
|
23
|
+
/** Tool name prefix */
|
|
24
|
+
export declare const TOOL_PREFIX: string;
|
|
25
|
+
/** Account storage filename */
|
|
26
|
+
export declare const ACCOUNTS_FILENAME: string;
|
|
27
|
+
/** Plan display labels derived from adapter */
|
|
28
|
+
export declare const PLAN_LABELS: import("opencode-multi-account-core").OAuthAdapterPlanLabels;
|
|
29
|
+
/** Access token expiry buffer (refresh 60s before expiry) */
|
|
30
|
+
export declare const TOKEN_EXPIRY_BUFFER_MS = 60000;
|
|
31
|
+
/** Maximum time to wait for a token refresh HTTP request */
|
|
32
|
+
export declare const TOKEN_REFRESH_TIMEOUT_MS = 30000;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
declare const executeWithAccountRotation: (manager: import("opencode-multi-account-core").ExecutorAccountManager, runtimeFactory: import("opencode-multi-account-core").ExecutorRuntimeFactory, client: import("opencode-multi-account-core").PluginClient, input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
2
|
+
export { executeWithAccountRotation };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { OPENAI_OAUTH_ADAPTER } from "./constants";
|
|
2
|
+
export declare const CodexMultiAuthPlugin: (ctx: unknown) => Promise<{
|
|
3
|
+
tool: {
|
|
4
|
+
[OPENAI_OAUTH_ADAPTER.statusToolName]: {
|
|
5
|
+
description: string;
|
|
6
|
+
args: {};
|
|
7
|
+
execute(args: Record<string, never>, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
auth: {
|
|
11
|
+
provider: string;
|
|
12
|
+
methods: ({
|
|
13
|
+
label: string;
|
|
14
|
+
type: "oauth";
|
|
15
|
+
authorize(): Promise<import("./auth-handler").OAuthFlowResult>;
|
|
16
|
+
} | {
|
|
17
|
+
type: "api";
|
|
18
|
+
label: string;
|
|
19
|
+
authorize?: undefined;
|
|
20
|
+
})[];
|
|
21
|
+
loader(getAuth: () => Promise<unknown>, provider: Record<string, unknown>): Promise<{
|
|
22
|
+
apiKey: string;
|
|
23
|
+
fetch: typeof fetch;
|
|
24
|
+
}>;
|
|
25
|
+
};
|
|
26
|
+
}>;
|
package/dist/index.js
CHANGED
|
@@ -1739,7 +1739,7 @@ async function confirm(message, defaultYes = false) {
|
|
|
1739
1739
|
return result ?? false;
|
|
1740
1740
|
}
|
|
1741
1741
|
|
|
1742
|
-
// ../
|
|
1742
|
+
// ../multi-account-core/src/adapters/openai.ts
|
|
1743
1743
|
var ISSUER = "https://auth.openai.com";
|
|
1744
1744
|
var openAIOAuthAdapter = {
|
|
1745
1745
|
id: "openai",
|
|
@@ -2345,11 +2345,11 @@ function getUsageSummary(account) {
|
|
|
2345
2345
|
const parts = [];
|
|
2346
2346
|
const { five_hour, seven_day } = parsed.output;
|
|
2347
2347
|
if (five_hour) {
|
|
2348
|
-
const reset = five_hour.resets_at ? ` (resets ${formatTimeRemaining(five_hour.resets_at)})` : "";
|
|
2348
|
+
const reset = five_hour.utilization >= 100 && five_hour.resets_at ? ` (resets ${formatTimeRemaining(five_hour.resets_at)})` : "";
|
|
2349
2349
|
parts.push(`5h: ${five_hour.utilization.toFixed(0)}%${reset}`);
|
|
2350
2350
|
}
|
|
2351
2351
|
if (seven_day) {
|
|
2352
|
-
const reset = seven_day.resets_at ? ` (resets ${formatTimeRemaining(seven_day.resets_at)})` : "";
|
|
2352
|
+
const reset = seven_day.utilization >= 100 && seven_day.resets_at ? ` (resets ${formatTimeRemaining(seven_day.resets_at)})` : "";
|
|
2353
2353
|
parts.push(`7d: ${seven_day.utilization.toFixed(0)}%${reset}`);
|
|
2354
2354
|
}
|
|
2355
2355
|
return parts.length > 0 ? parts.join(", ") : "no usage data";
|
|
@@ -2408,6 +2408,16 @@ function getAccountStatus(account) {
|
|
|
2408
2408
|
if (account.isAuthDisabled) return "auth-disabled";
|
|
2409
2409
|
if (!account.enabled) return "disabled";
|
|
2410
2410
|
if (account.rateLimitResetAt && account.rateLimitResetAt > Date.now()) return "rate-limited";
|
|
2411
|
+
if (account.cachedUsage) {
|
|
2412
|
+
const now = Date.now();
|
|
2413
|
+
const usage = account.cachedUsage;
|
|
2414
|
+
const exhaustedTiers = [usage.five_hour, usage.seven_day].filter(
|
|
2415
|
+
(tier) => tier && tier.utilization >= 100 && tier.resets_at != null && Date.parse(tier.resets_at) > now
|
|
2416
|
+
);
|
|
2417
|
+
if (exhaustedTiers.length > 0) {
|
|
2418
|
+
return "rate-limited";
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2411
2421
|
return "active";
|
|
2412
2422
|
}
|
|
2413
2423
|
var STATUS_BADGE = {
|
|
@@ -2440,7 +2450,7 @@ async function showAuthMenu(accounts) {
|
|
|
2440
2450
|
while (true) {
|
|
2441
2451
|
const subtitle = `${accounts.length} account(s) registered`;
|
|
2442
2452
|
const result = await select(items, {
|
|
2443
|
-
message: "
|
|
2453
|
+
message: "Codex Multi-Auth",
|
|
2444
2454
|
subtitle
|
|
2445
2455
|
});
|
|
2446
2456
|
if (!result) return { type: "cancel" };
|
|
@@ -2527,8 +2537,8 @@ function printUsageEntry(name, entry, isLast) {
|
|
|
2527
2537
|
return;
|
|
2528
2538
|
}
|
|
2529
2539
|
const bar = createProgressBar(entry.utilization);
|
|
2530
|
-
const
|
|
2531
|
-
console.log(` ${connector} ${name.padEnd(16)} ${bar}${
|
|
2540
|
+
const resetInfo = entry.utilization >= 100 && entry.resets_at ? formatResetTime(entry.resets_at) : "";
|
|
2541
|
+
console.log(` ${connector} ${name.padEnd(16)} ${bar}${resetInfo}`);
|
|
2532
2542
|
}
|
|
2533
2543
|
function printQuotaReport(account, usage) {
|
|
2534
2544
|
const label = getAccountLabel(account);
|
|
@@ -3110,6 +3120,9 @@ var AccountRuntimeFactory = class {
|
|
|
3110
3120
|
};
|
|
3111
3121
|
|
|
3112
3122
|
// src/index.ts
|
|
3123
|
+
function formatResetTime2(resetAt) {
|
|
3124
|
+
return formatWaitTime(new Date(resetAt).getTime() - Date.now());
|
|
3125
|
+
}
|
|
3113
3126
|
var CodexMultiAuthPlugin = async (ctx) => {
|
|
3114
3127
|
const { client } = ctx;
|
|
3115
3128
|
await loadConfig();
|
|
@@ -3147,6 +3160,19 @@ var CodexMultiAuthPlugin = async (ctx) => {
|
|
|
3147
3160
|
const remaining = formatWaitTime(account.rateLimitResetAt - Date.now());
|
|
3148
3161
|
statusParts.push(`RATE LIMITED (resets in ${remaining})`);
|
|
3149
3162
|
}
|
|
3163
|
+
if (account.cachedUsage) {
|
|
3164
|
+
const usage2 = account.cachedUsage;
|
|
3165
|
+
const exhaustedTiers = [
|
|
3166
|
+
{ name: "5-hour", tier: usage2.five_hour },
|
|
3167
|
+
{ name: "7-day", tier: usage2.seven_day }
|
|
3168
|
+
].filter(({ tier }) => tier && tier.utilization >= 100);
|
|
3169
|
+
exhaustedTiers.forEach(({ name, tier }) => {
|
|
3170
|
+
if (tier && tier.resets_at) {
|
|
3171
|
+
const resetTime = formatResetTime2(tier.resets_at);
|
|
3172
|
+
statusParts.push(`USAGE EXHAUSTED (${name}, resets ${resetTime})`);
|
|
3173
|
+
}
|
|
3174
|
+
});
|
|
3175
|
+
}
|
|
3150
3176
|
lines.push(
|
|
3151
3177
|
`- **${label}**${marker}: ${statusParts.join(" | ")} | ${usage}`
|
|
3152
3178
|
);
|
package/dist/oauth.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { TokenResponse } from "./types";
|
|
2
|
+
type PKCE = {
|
|
3
|
+
verifier: string;
|
|
4
|
+
challenge: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function generatePKCE(): Promise<{
|
|
7
|
+
verifier: string;
|
|
8
|
+
challenge: string;
|
|
9
|
+
}>;
|
|
10
|
+
export declare function generateState(): string;
|
|
11
|
+
export declare function exchangeCodeForTokens(code: string, redirectUri: string, pkce: PKCE): Promise<TokenResponse>;
|
|
12
|
+
export declare function refreshAccessToken(refreshToken: string): Promise<TokenResponse>;
|
|
13
|
+
export interface IdTokenClaims {
|
|
14
|
+
chatgpt_account_id?: string;
|
|
15
|
+
organizations?: Array<{
|
|
16
|
+
id: string;
|
|
17
|
+
}>;
|
|
18
|
+
email?: string;
|
|
19
|
+
"https://api.openai.com/auth"?: {
|
|
20
|
+
chatgpt_account_id?: string;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export declare function parseJwtClaims(token: string): IdTokenClaims | undefined;
|
|
24
|
+
export declare function extractAccountId(tokens: {
|
|
25
|
+
id_token?: string;
|
|
26
|
+
access_token: string;
|
|
27
|
+
}): string | undefined;
|
|
28
|
+
export declare function startOAuthServer(): Promise<{
|
|
29
|
+
port: number;
|
|
30
|
+
redirectUri: string;
|
|
31
|
+
}>;
|
|
32
|
+
export declare function stopOAuthServer(): void;
|
|
33
|
+
export declare function waitForOAuthCallback(pkce: PKCE, state: string): Promise<TokenResponse>;
|
|
34
|
+
export declare function buildAuthorizeUrl(redirectUri: string, pkce: {
|
|
35
|
+
challenge: string;
|
|
36
|
+
}, state: string): string;
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
declare const fetchUsageLimits: (accessToken: string, accountId?: string) => Promise<import("opencode-multi-account-core").UsageLimits | null>, getResetMsFromUsage: (account: import("opencode-multi-account-core").ManagedAccount) => number | null, handleRateLimitResponse: (manager: import("opencode-multi-account-core").RateLimitAccountManager, client: import("opencode-multi-account-core").PluginClient, account: import("opencode-multi-account-core").ManagedAccount, response: Response) => Promise<void>, retryAfterMsFromResponse: (response: Response) => number;
|
|
2
|
+
export { fetchUsageLimits, getResetMsFromUsage, handleRateLimitResponse, retryAfterMsFromResponse, };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { AccountStore } from "./account-store";
|
|
2
|
+
import type { PluginClient } from "./types";
|
|
3
|
+
type BaseFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
4
|
+
interface AccountRuntime {
|
|
5
|
+
fetch: BaseFetch;
|
|
6
|
+
}
|
|
7
|
+
export declare class AccountRuntimeFactory {
|
|
8
|
+
private readonly store;
|
|
9
|
+
private readonly client;
|
|
10
|
+
private runtimes;
|
|
11
|
+
private initLocks;
|
|
12
|
+
constructor(store: AccountStore, client: PluginClient);
|
|
13
|
+
getRuntime(uuid: string): Promise<AccountRuntime>;
|
|
14
|
+
invalidate(uuid: string): void;
|
|
15
|
+
invalidateAll(): void;
|
|
16
|
+
private createRuntime;
|
|
17
|
+
}
|
|
18
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { deduplicateAccounts, loadAccounts, readStorageFromDisk, } from "opencode-multi-account-core";
|
package/dist/token.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type ManagedAccount, type PluginClient, type TokenRefreshResult } from "./types";
|
|
2
|
+
export declare function isTokenExpired(account: Pick<ManagedAccount, "accessToken" | "expiresAt">): boolean;
|
|
3
|
+
export declare function refreshToken(currentRefreshToken: string, accountId: string, client: PluginClient): Promise<TokenRefreshResult>;
|
|
4
|
+
export declare function clearRefreshMutex(accountId?: string): void;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
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
|
+
/** OpenAI /oauth/token response */
|
|
103
|
+
export declare const TokenResponseSchema: v.ObjectSchema<{
|
|
104
|
+
readonly id_token: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
105
|
+
readonly access_token: v.StringSchema<undefined>;
|
|
106
|
+
readonly refresh_token: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
107
|
+
readonly expires_in: v.NumberSchema<undefined>;
|
|
108
|
+
}, undefined>;
|
|
109
|
+
export type OAuthCredentials = v.InferOutput<typeof OAuthCredentialsSchema>;
|
|
110
|
+
export type UsageLimitEntry = v.InferOutput<typeof UsageLimitEntrySchema>;
|
|
111
|
+
export type UsageLimits = v.InferOutput<typeof UsageLimitsSchema>;
|
|
112
|
+
export type CredentialRefreshPatch = v.InferOutput<typeof CredentialRefreshPatchSchema>;
|
|
113
|
+
export type StoredAccount = v.InferOutput<typeof StoredAccountSchema>;
|
|
114
|
+
export type AccountStorage = v.InferOutput<typeof AccountStorageSchema>;
|
|
115
|
+
export type TokenResponse = v.InferOutput<typeof TokenResponseSchema>;
|
|
116
|
+
export declare const AccountSelectionStrategySchema: v.PicklistSchema<["sticky", "round-robin", "hybrid"], undefined>;
|
|
117
|
+
export type AccountSelectionStrategy = v.InferOutput<typeof AccountSelectionStrategySchema>;
|
|
118
|
+
export declare const PluginConfigSchema: v.ObjectSchema<{
|
|
119
|
+
/** sticky: same account until failure, round-robin: rotate every request, hybrid: health+usage scoring */
|
|
120
|
+
readonly account_selection_strategy: v.OptionalSchema<v.PicklistSchema<["sticky", "round-robin", "hybrid"], undefined>, "sticky">;
|
|
121
|
+
/** Use cross-process claim file to distribute parallel sessions across accounts */
|
|
122
|
+
readonly cross_process_claims: v.OptionalSchema<v.BooleanSchema<undefined>, true>;
|
|
123
|
+
/** Skip account when any usage tier utilization >= this % (100 = disabled) */
|
|
124
|
+
readonly soft_quota_threshold_percent: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 0, undefined>, v.MaxValueAction<number, 100, undefined>]>, 100>;
|
|
125
|
+
/** Minimum backoff after rate limit (ms) */
|
|
126
|
+
readonly rate_limit_min_backoff_ms: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 0, undefined>]>, 30000>;
|
|
127
|
+
/** Default retry-after when header is missing (ms) */
|
|
128
|
+
readonly default_retry_after_ms: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 0, undefined>]>, 60000>;
|
|
129
|
+
/** Consecutive auth failures before disabling account */
|
|
130
|
+
readonly max_consecutive_auth_failures: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 1, undefined>]>, 3>;
|
|
131
|
+
/** Backoff after token refresh failure (ms) */
|
|
132
|
+
readonly token_failure_backoff_ms: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 0, undefined>]>, 30000>;
|
|
133
|
+
/** Enable proactive background token refresh */
|
|
134
|
+
readonly proactive_refresh: v.OptionalSchema<v.BooleanSchema<undefined>, true>;
|
|
135
|
+
/** Seconds before expiry to trigger proactive refresh (default 30 min) */
|
|
136
|
+
readonly proactive_refresh_buffer_seconds: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 60, undefined>]>, 1800>;
|
|
137
|
+
/** Interval between background refresh checks in seconds (default 5 min) */
|
|
138
|
+
readonly proactive_refresh_interval_seconds: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 30, undefined>]>, 300>;
|
|
139
|
+
/** Suppress toast notifications */
|
|
140
|
+
readonly quiet_mode: v.OptionalSchema<v.BooleanSchema<undefined>, false>;
|
|
141
|
+
/** Enable debug logging */
|
|
142
|
+
readonly debug: v.OptionalSchema<v.BooleanSchema<undefined>, false>;
|
|
143
|
+
}, undefined>;
|
|
144
|
+
export type PluginConfig = v.InferOutput<typeof PluginConfigSchema>;
|
|
145
|
+
export interface OriginalAuthHook {
|
|
146
|
+
methods?: Array<{
|
|
147
|
+
authorize?: (inputs?: Record<string, string>) => Promise<unknown>;
|
|
148
|
+
}>;
|
|
149
|
+
loader: (getAuth: () => Promise<unknown>, provider: unknown) => Promise<{
|
|
150
|
+
apiKey: string;
|
|
151
|
+
fetch: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
152
|
+
}>;
|
|
153
|
+
}
|
|
154
|
+
export type TokenRefreshResult = {
|
|
155
|
+
ok: true;
|
|
156
|
+
patch: CredentialRefreshPatch;
|
|
157
|
+
} | {
|
|
158
|
+
ok: false;
|
|
159
|
+
permanent: boolean;
|
|
160
|
+
status?: number;
|
|
161
|
+
};
|
|
162
|
+
export interface ManagedAccount {
|
|
163
|
+
index: number;
|
|
164
|
+
uuid?: string;
|
|
165
|
+
accountId?: string;
|
|
166
|
+
label?: string;
|
|
167
|
+
email?: string;
|
|
168
|
+
planTier?: string;
|
|
169
|
+
refreshToken: string;
|
|
170
|
+
accessToken?: string;
|
|
171
|
+
expiresAt?: number;
|
|
172
|
+
addedAt: number;
|
|
173
|
+
lastUsed: number;
|
|
174
|
+
enabled: boolean;
|
|
175
|
+
rateLimitResetAt?: number;
|
|
176
|
+
last429At?: number;
|
|
177
|
+
cachedUsage?: UsageLimits;
|
|
178
|
+
cachedUsageAt?: number;
|
|
179
|
+
consecutiveAuthFailures: number;
|
|
180
|
+
isAuthDisabled: boolean;
|
|
181
|
+
authDisabledReason?: string;
|
|
182
|
+
}
|
|
183
|
+
export interface PluginClient {
|
|
184
|
+
auth: {
|
|
185
|
+
set: (params: {
|
|
186
|
+
path: {
|
|
187
|
+
id: string;
|
|
188
|
+
};
|
|
189
|
+
body: {
|
|
190
|
+
type: string;
|
|
191
|
+
refresh: string;
|
|
192
|
+
access: string;
|
|
193
|
+
expires: number;
|
|
194
|
+
};
|
|
195
|
+
}) => Promise<void>;
|
|
196
|
+
};
|
|
197
|
+
tui: {
|
|
198
|
+
showToast: (params: {
|
|
199
|
+
body: {
|
|
200
|
+
title?: string;
|
|
201
|
+
message: string;
|
|
202
|
+
variant: "info" | "warning" | "success" | "error";
|
|
203
|
+
};
|
|
204
|
+
}) => Promise<void>;
|
|
205
|
+
};
|
|
206
|
+
app: {
|
|
207
|
+
log: (params: {
|
|
208
|
+
body: {
|
|
209
|
+
service: string;
|
|
210
|
+
level: "debug" | "info" | "warn" | "error";
|
|
211
|
+
message: string;
|
|
212
|
+
extra?: Record<string, unknown>;
|
|
213
|
+
};
|
|
214
|
+
}) => Promise<void>;
|
|
215
|
+
};
|
|
216
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ANSI, isTTY, parseKey, type KeyAction, } from "opencode-multi-account-core";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { AccountSelectionStrategy, ManagedAccount, UsageLimits } from "../types";
|
|
2
|
+
export type AuthMenuAction = {
|
|
3
|
+
type: "add";
|
|
4
|
+
} | {
|
|
5
|
+
type: "check-quotas";
|
|
6
|
+
} | {
|
|
7
|
+
type: "manage";
|
|
8
|
+
} | {
|
|
9
|
+
type: "load-balancing";
|
|
10
|
+
} | {
|
|
11
|
+
type: "delete-all";
|
|
12
|
+
} | {
|
|
13
|
+
type: "cancel";
|
|
14
|
+
};
|
|
15
|
+
export type AccountAction = "back" | "toggle" | "delete" | "retry-auth" | "cancel";
|
|
16
|
+
type AccountStatus = "active" | "rate-limited" | "auth-disabled" | "disabled";
|
|
17
|
+
export declare function getAccountStatus(account: ManagedAccount): AccountStatus;
|
|
18
|
+
export declare function showAuthMenu(accounts: ManagedAccount[]): Promise<AuthMenuAction>;
|
|
19
|
+
export declare function showManageAccounts(accounts: ManagedAccount[]): Promise<{
|
|
20
|
+
action: AccountAction;
|
|
21
|
+
account?: ManagedAccount;
|
|
22
|
+
}>;
|
|
23
|
+
export declare function printQuotaReport(account: ManagedAccount, usage: UsageLimits): void;
|
|
24
|
+
export declare function showStrategySelect(current: AccountSelectionStrategy): Promise<AccountSelectionStrategy | null>;
|
|
25
|
+
export type AuthMethod = "browser" | "headless";
|
|
26
|
+
export declare function showMethodSelect(): Promise<AuthMethod | null>;
|
|
27
|
+
export declare function printQuotaError(account: ManagedAccount, error: string): void;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { confirm } from "opencode-multi-account-core";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { select, type MenuItem, type SelectOptions, } from "opencode-multi-account-core";
|
package/dist/usage.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ManagedAccount, UsageLimits } from "./types";
|
|
2
|
+
export type ProfileData = {
|
|
3
|
+
email?: string;
|
|
4
|
+
planTier: string;
|
|
5
|
+
};
|
|
6
|
+
export type FetchUsageResult = {
|
|
7
|
+
ok: true;
|
|
8
|
+
data: UsageLimits;
|
|
9
|
+
planType?: string;
|
|
10
|
+
} | {
|
|
11
|
+
ok: false;
|
|
12
|
+
reason: string;
|
|
13
|
+
};
|
|
14
|
+
export type FetchProfileResult = {
|
|
15
|
+
ok: true;
|
|
16
|
+
data: ProfileData;
|
|
17
|
+
} | {
|
|
18
|
+
ok: false;
|
|
19
|
+
reason: string;
|
|
20
|
+
};
|
|
21
|
+
export declare function fetchUsage(accessToken: string, accountId?: string): Promise<FetchUsageResult>;
|
|
22
|
+
export declare function derivePlanTier(planType: string): string;
|
|
23
|
+
export declare function fetchProfile(accessToken: string): FetchProfileResult;
|
|
24
|
+
export declare function getUsageSummary(account: ManagedAccount): string;
|
|
25
|
+
export declare function getPlanLabel(account: ManagedAccount): string;
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createMinimalClient, debugLog, formatWaitTime, getAccountLabel, getConfigDir, getErrorCode, showToast, sleep, } from "opencode-multi-account-core";
|
package/package.json
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-codex-multi-account",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "OpenCode plugin for Codex (OpenAI) multi-account management with automatic rate limit switching",
|
|
5
|
-
"main": "./
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
7
8
|
"exports": {
|
|
8
9
|
".": {
|
|
9
|
-
"
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
10
11
|
"import": "./dist/index.js",
|
|
11
12
|
"default": "./dist/index.js"
|
|
12
13
|
}
|
|
13
14
|
},
|
|
14
15
|
"scripts": {
|
|
15
|
-
"build": "esbuild src/index.ts --bundle --outdir=dist --platform=node --format=esm --packages=external",
|
|
16
|
+
"build": "esbuild src/index.ts --bundle --outdir=dist --platform=node --format=esm --packages=external && tsc -p tsconfig.build.json --emitDeclarationOnly",
|
|
16
17
|
"typecheck": "tsc --noEmit",
|
|
17
|
-
"test": "
|
|
18
|
+
"test": "for f in tests/*.test.ts; do bun test \"$f\" || exit 1; done",
|
|
18
19
|
"prepack": "bun run build",
|
|
19
20
|
"dev": "bun run build --watch"
|
|
20
21
|
},
|
|
@@ -39,15 +40,12 @@
|
|
|
39
40
|
"directory": "packages/codex-multi-account"
|
|
40
41
|
},
|
|
41
42
|
"dependencies": {
|
|
42
|
-
"opencode-multi-account-core": "^0.1
|
|
43
|
-
"opencode-oauth-adapters": "^0.1.2",
|
|
44
|
-
"proper-lockfile": "^4.1.2",
|
|
43
|
+
"opencode-multi-account-core": "^0.2.1",
|
|
45
44
|
"valibot": "^1.2.0"
|
|
46
45
|
},
|
|
47
46
|
"devDependencies": {
|
|
48
47
|
"@opencode-ai/plugin": "^0.15.30",
|
|
49
48
|
"@types/node": "^25.2.2",
|
|
50
|
-
"@types/proper-lockfile": "^4.1.4",
|
|
51
49
|
"typescript": "^5.8.0"
|
|
52
50
|
},
|
|
53
51
|
"publishConfig": {
|