opencode-auth-load-balancer 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/LICENSE +21 -0
- package/README.md +289 -0
- package/dist/accounts.d.ts +20 -0
- package/dist/cli/status.d.ts +2 -0
- package/dist/fetch.d.ts +34 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +14829 -0
- package/dist/notify.d.ts +47 -0
- package/dist/pool/lock.d.ts +39 -0
- package/dist/pool/paths.d.ts +16 -0
- package/dist/pool/store.d.ts +51 -0
- package/dist/prime.d.ts +32 -0
- package/dist/providers/anthropic/adapter.d.ts +8 -0
- package/dist/providers/anthropic/cch.d.ts +20 -0
- package/dist/providers/anthropic/constants.d.ts +64 -0
- package/dist/providers/anthropic/fallback.d.ts +52 -0
- package/dist/providers/anthropic/oauth.d.ts +8 -0
- package/dist/providers/anthropic/transform.d.ts +23 -0
- package/dist/providers/anthropic/usage.d.ts +14 -0
- package/dist/providers/headers.d.ts +7 -0
- package/dist/providers/http-timeouts.d.ts +32 -0
- package/dist/providers/oauth-callback.d.ts +100 -0
- package/dist/providers/openai/adapter.d.ts +9 -0
- package/dist/providers/openai/constants.d.ts +18 -0
- package/dist/providers/openai/jwt.d.ts +5 -0
- package/dist/providers/openai/oauth.d.ts +12 -0
- package/dist/providers/openai/transform.d.ts +18 -0
- package/dist/providers/openai/usage.d.ts +16 -0
- package/dist/providers/pkce.d.ts +10 -0
- package/dist/providers/types.d.ts +122 -0
- package/dist/providers/usage-headers.d.ts +55 -0
- package/dist/providers/usage-http.d.ts +8 -0
- package/dist/refresh.d.ts +13 -0
- package/dist/scheduler/config.d.ts +46 -0
- package/dist/scheduler/score-core.d.ts +167 -0
- package/dist/scheduler/select.d.ts +41 -0
- package/dist/session.d.ts +15 -0
- package/dist/status.d.ts +55 -0
- package/dist/types.d.ts +101 -0
- package/dist/usage-merge.d.ts +23 -0
- package/dist/usage-refresh.d.ts +30 -0
- package/dist/util.d.ts +118 -0
- package/package.json +75 -0
- package/tui/auth-load-balancer-scoring.ts +344 -0
- package/tui/auth-load-balancer-tui.logic.ts +362 -0
- package/tui/auth-load-balancer-tui.ts +39 -0
- package/tui/auth-load-balancer-tui.view.tsx +327 -0
package/dist/notify.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { PoolAccount } from './types';
|
|
2
|
+
/** The slice of the opencode SDK client we need to show a toast. */
|
|
3
|
+
export interface ToastClient {
|
|
4
|
+
tui: {
|
|
5
|
+
showToast(opts: {
|
|
6
|
+
body: {
|
|
7
|
+
title?: string;
|
|
8
|
+
message: string;
|
|
9
|
+
variant: 'info' | 'success' | 'warning' | 'error';
|
|
10
|
+
duration?: number;
|
|
11
|
+
};
|
|
12
|
+
}): Promise<unknown>;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Toast (once) when a provider's in-use account changes — the user's "which account
|
|
17
|
+
* is in use" indicator. De-duped per provider so a sticky session doesn't re-toast.
|
|
18
|
+
* Best-effort: a failed toast never affects the request.
|
|
19
|
+
*/
|
|
20
|
+
export declare function notifyOnSwitch(client: ToastClient, providerID: string, account: PoolAccount): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Toast (once per account + source-model + exhaustion window) when a request's
|
|
23
|
+
* model was auto-downgraded to a fallback because its MODEL TIER's weekly cap
|
|
24
|
+
* is exhausted — so the downgrade is visible, not silent. The de-dupe value is
|
|
25
|
+
* scoped to the latest tier exhaustion window (the max entry of
|
|
26
|
+
* `modelCooldownsUntil`): turns within one window toast once, but after the
|
|
27
|
+
* tier cap RESETS and later re-exhausts (a NEW window, hence a new cooldown
|
|
28
|
+
* timestamp) the downgrade toasts again — otherwise every window after the
|
|
29
|
+
* first in a process's lifetime would be silent. A source-model change
|
|
30
|
+
* re-toasts within a window too. The scan ignores tier entries that have
|
|
31
|
+
* already expired (`until <= now`): nothing in the codebase purges a stale
|
|
32
|
+
* `modelCooldownsUntil[tier]` entry once its window passes, so an old,
|
|
33
|
+
* long-reset tier's timestamp can otherwise outrank a fresh, currently-active
|
|
34
|
+
* tier's smaller timestamp and mask a genuinely NEW exhaustion window behind
|
|
35
|
+
* an unchanged de-dupe key — silently suppressing the toast this function
|
|
36
|
+
* exists to guarantee. When the caller supplies `fromTier` (the SPECIFIC
|
|
37
|
+
* tier that triggered this downgrade — `ModelFallback.fromTier`, threaded
|
|
38
|
+
* through from `downgradeModel`), that tier's own window is used directly
|
|
39
|
+
* instead of the max-scan: with two tiers simultaneously active on one
|
|
40
|
+
* account (e.g. `fable` capped, downgrade to `opus`, `opus` ALSO capped,
|
|
41
|
+
* downgrade to `sonnet`) the max-scan could pick whichever window is later,
|
|
42
|
+
* not necessarily the one that actually triggered this toast. Callers that
|
|
43
|
+
* don't supply `fromTier` (older call sites, or a provider with no tier
|
|
44
|
+
* concept) keep the max-scan heuristic unchanged. Best-effort: a failed
|
|
45
|
+
* toast never affects the request.
|
|
46
|
+
*/
|
|
47
|
+
export declare function notifyModelFallback(client: ToastClient, providerID: string, account: PoolAccount, fromModel: string, toModel: string, fromTier?: string): Promise<void>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-process advisory file lock.
|
|
3
|
+
*
|
|
4
|
+
* The in-process promise-chain mutex in `store.ts` only serializes mutations
|
|
5
|
+
* within ONE opencode process. Two opencode instances (two TUI windows, a server
|
|
6
|
+
* + a TUI, two project dirs) share the single pool file and can therefore race a
|
|
7
|
+
* read-modify-write — or, worse, both spend the same single-use OAuth refresh
|
|
8
|
+
* token and brick an account. This lock serializes those critical sections across
|
|
9
|
+
* processes using an atomic `mkdir` (which fails if the directory already exists
|
|
10
|
+
* on every OS) as the mutual-exclusion primitive.
|
|
11
|
+
*
|
|
12
|
+
* It is advisory and best-effort: a crashed holder is reclaimed after `staleMs`
|
|
13
|
+
* with no heartbeat. The lock SHRINKS the race window; the `tokenGen` generation
|
|
14
|
+
* guard in `refresh.ts` is the data-level backstop that keeps the residual window
|
|
15
|
+
* safe (a superseded refresher adopts the winner's token instead of disabling).
|
|
16
|
+
*/
|
|
17
|
+
/** Tunable lock timing. All values in ms. */
|
|
18
|
+
export interface LockOptions {
|
|
19
|
+
/** A held lock whose heartbeat has not advanced for this long is treated as crashed. */
|
|
20
|
+
readonly staleMs: number;
|
|
21
|
+
/** Give up acquiring after this long, throwing LockTimeoutError. */
|
|
22
|
+
readonly timeoutMs: number;
|
|
23
|
+
/** Base poll interval between acquisition attempts (jittered up to 2x). */
|
|
24
|
+
readonly retryMs: number;
|
|
25
|
+
/** Re-touch the held lock this often so a live holder never looks stale. */
|
|
26
|
+
readonly heartbeatMs: number;
|
|
27
|
+
}
|
|
28
|
+
interface LockHandle {
|
|
29
|
+
release(): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
export declare class LockTimeoutError extends Error {
|
|
32
|
+
readonly lockDir: string;
|
|
33
|
+
constructor(lockDir: string);
|
|
34
|
+
}
|
|
35
|
+
/** Acquire `lockDir`, waiting for a current holder up to `timeoutMs`. */
|
|
36
|
+
export declare function acquireLock(lockDir: string, opts: LockOptions): Promise<LockHandle>;
|
|
37
|
+
/** Run `fn` while holding `lockDir`, releasing it even if `fn` throws. */
|
|
38
|
+
export declare function withLock<T>(lockDir: string, opts: LockOptions, fn: () => Promise<T>): Promise<T>;
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface DataDirEnv {
|
|
2
|
+
override?: string | undefined;
|
|
3
|
+
xdgDataHome?: string | undefined;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Resolve opencode's data directory, matching opencode-core's `xdg-basedir` usage:
|
|
7
|
+
* $XDG_DATA_HOME/opencode or ~/.local/share/opencode
|
|
8
|
+
*
|
|
9
|
+
* xdg-basedir is platform-agnostic — it does NOT special-case Windows or macOS, so
|
|
10
|
+
* opencode uses ~/.local/share/opencode on every OS (verified with `opencode debug
|
|
11
|
+
* paths` on Windows). OPENCODE_AUTH_LB_DIR overrides everything (handy for tests).
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveDataDir(env: DataDirEnv, home: string): string;
|
|
14
|
+
/** Path to the load-balancer's credential pool file. */
|
|
15
|
+
export declare function poolFilePath(): string;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type PoolAccount, type PoolFile } from '../types';
|
|
2
|
+
/** Thrown when the pool file cannot be written atomically after retries. */
|
|
3
|
+
export declare class PoolWriteError extends Error {
|
|
4
|
+
readonly path: string;
|
|
5
|
+
readonly reason: unknown;
|
|
6
|
+
constructor(path: string, reason: unknown);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Thrown when the pool file exists but cannot be READ (EACCES, EMFILE, Windows
|
|
10
|
+
* EBUSY/EPERM from an AV tool, EISDIR…). Deliberately distinct from the two
|
|
11
|
+
* tolerated read outcomes (missing file, corrupt JSON → empty pool): inside
|
|
12
|
+
* `mutatePool` a swallowed transient read fault would mutate an EMPTY pool and
|
|
13
|
+
* write it back — atomically WIPING every registered account. Bubbling a typed
|
|
14
|
+
* error lets `bestEffort` skip the bookkeeping write instead.
|
|
15
|
+
*/
|
|
16
|
+
export declare class PoolReadError extends Error {
|
|
17
|
+
readonly path: string;
|
|
18
|
+
readonly reason: unknown;
|
|
19
|
+
constructor(path: string, reason: unknown);
|
|
20
|
+
}
|
|
21
|
+
/** Injectable fs surface so the atomic-write fallback path is unit-testable. */
|
|
22
|
+
export interface FsOps {
|
|
23
|
+
mkdir: (path: string, opts: {
|
|
24
|
+
recursive: boolean;
|
|
25
|
+
}) => Promise<unknown>;
|
|
26
|
+
writeFile: (path: string, data: string, opts: {
|
|
27
|
+
mode: number;
|
|
28
|
+
}) => Promise<void>;
|
|
29
|
+
rename: (from: string, to: string) => Promise<void>;
|
|
30
|
+
unlink: (path: string) => Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Write JSON atomically: temp file + rename (atomic on POSIX and modern Windows).
|
|
34
|
+
* If the rename fails — e.g. a concurrent process briefly holds the target open
|
|
35
|
+
* (Windows EPERM) — retry a few times, then clean up the temp file and throw. We
|
|
36
|
+
* never fall back to a direct overwrite: that sacrifices atomicity and can shred a
|
|
37
|
+
* concurrent writer's update. Mutations already run under the cross-process pool
|
|
38
|
+
* lock, so a sustained rename failure is a real disk fault worth surfacing.
|
|
39
|
+
*/
|
|
40
|
+
export declare function writeJsonAtomic(path: string, payload: string, ops?: FsOps): Promise<void>;
|
|
41
|
+
/** Read the pool (serialized against in-flight mutations). */
|
|
42
|
+
export declare function readPool(): Promise<PoolFile>;
|
|
43
|
+
/**
|
|
44
|
+
* Atomically read-modify-write the pool. The callback mutates `pool` in place;
|
|
45
|
+
* its return value is forwarded to the caller.
|
|
46
|
+
*/
|
|
47
|
+
export declare function mutatePool<T>(fn: (pool: PoolFile) => T | Promise<T>): Promise<T>;
|
|
48
|
+
/** Find an account by id within a pool object. */
|
|
49
|
+
export declare function findAccount(pool: PoolFile, id: string): PoolAccount | undefined;
|
|
50
|
+
/** Read a single account by id from the on-disk pool (serialized read). */
|
|
51
|
+
export declare function readPoolAccount(id: string): Promise<PoolAccount | undefined>;
|
package/dist/prime.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Point the "in-use" marker at the top-ranked account for a provider. Startup is a
|
|
3
|
+
* clean-context moment — no session is pinned yet — so the next request will pick this
|
|
4
|
+
* account anyway; reflecting it immediately means the dashboard shows the account that
|
|
5
|
+
* will actually be used next, not whichever one happened to serve last. Best-effort.
|
|
6
|
+
*
|
|
7
|
+
* The selection is folded INTO the `mutatePool` callback so it runs against the
|
|
8
|
+
* just-locked pool snapshot we are about to write. That closes the small TOCTOU
|
|
9
|
+
* window between a prior `readPool()` and this write (another opencode process could
|
|
10
|
+
* re-rank, a cooldown could apply, or an account could be deleted via the TUI in
|
|
11
|
+
* between) and saves the extra read on the hot startup path. The `lastSelected`
|
|
12
|
+
* field is informational, so even with the old window the commit was benign — but
|
|
13
|
+
* the invariant is now local ("we commit the account selected from the SAME snapshot
|
|
14
|
+
* we are writing").
|
|
15
|
+
*
|
|
16
|
+
* NOTE: this lives in its own module (not exported from `index.ts`) on purpose —
|
|
17
|
+
* opencode's plugin loader treats EVERY function exported by the plugin entry module
|
|
18
|
+
* as a plugin and invokes it, so a stray exported helper there gets called with the
|
|
19
|
+
* plugin input and its `undefined` return poisons the hook list.
|
|
20
|
+
*
|
|
21
|
+
* Fast path (same pattern as `bootstrapFromOpencodeAuth`): both provider plugins
|
|
22
|
+
* run this at every opencode startup, so without it every start would take the
|
|
23
|
+
* in-process mutex + cross-process file lock and atomically rewrite the pool
|
|
24
|
+
* file byte-identically — `selectAccount` over zero accounts changes nothing,
|
|
25
|
+
* and in the steady state (every restart after the first request)
|
|
26
|
+
* `lastSelected` already points at the top-ranked account. One cheap serialized
|
|
27
|
+
* read skips all of those no-op writes. No TOCTOU concern: the fast path only
|
|
28
|
+
* skips a write that would change nothing, an account added concurrently is
|
|
29
|
+
* primed by its own login/startup flow, and `lastSelected` is informational
|
|
30
|
+
* anyway (see above).
|
|
31
|
+
*/
|
|
32
|
+
export declare function primeInUse(providerID: string, now: number): Promise<void>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ProviderAdapter } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Anthropic (Claude Pro/Max OAuth) provider adapter.
|
|
4
|
+
*
|
|
5
|
+
* Login mode is fixed to "max" (subscription accounts) — the pool exists to
|
|
6
|
+
* balance subscription quota, not metered API keys.
|
|
7
|
+
*/
|
|
8
|
+
export declare const anthropicAdapter: ProviderAdapter;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
interface Message {
|
|
2
|
+
role?: string;
|
|
3
|
+
content?: string | Array<{
|
|
4
|
+
type?: string;
|
|
5
|
+
text?: string;
|
|
6
|
+
}>;
|
|
7
|
+
}
|
|
8
|
+
/** Compute cch: first 5 hex characters of SHA-256(messageText). */
|
|
9
|
+
export declare function computeCCH(messageText: string): string;
|
|
10
|
+
/** Compute the 3-char version suffix from the sampled message characters. */
|
|
11
|
+
export declare function computeVersionSuffix(text: string, version?: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Build the complete billing header string for insertion into system[0], or
|
|
14
|
+
* null when no user-role message exists (the single prefix scan here is both
|
|
15
|
+
* the has-user-message gate and the text-extraction source).
|
|
16
|
+
* A user message that exists but yields empty text still produces a header.
|
|
17
|
+
* Closure-free loop (not Array.find) — runs once per Anthropic request.
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildBillingHeaderValue(messages: (Message | null | undefined)[], entrypoint: string): string | null;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export declare const PROVIDER_ID = "anthropic";
|
|
2
|
+
export declare const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
3
|
+
export declare const AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
|
|
4
|
+
export declare const CODE_CALLBACK_URL = "https://platform.claude.com/oauth/code/callback";
|
|
5
|
+
export declare const TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
|
|
6
|
+
export { OAUTH_HTTP_TIMEOUT_MS, USAGE_HTTP_TIMEOUT_MS } from '../http-timeouts';
|
|
7
|
+
/** Dedicated usage endpoint — returns 5h + 7d utilization without consuming quota. */
|
|
8
|
+
export declare const USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
9
|
+
export declare const OAUTH_SCOPES: string[];
|
|
10
|
+
export declare const TOOL_PREFIX = "mcp_";
|
|
11
|
+
/**
|
|
12
|
+
* LAST-RESORT downgrade target when a MODEL-TIER weekly cap is exhausted and
|
|
13
|
+
* the fallback LADDER cannot produce a candidate (the provider's model catalog
|
|
14
|
+
* is empty or has no model in a lower family). The normal path walks
|
|
15
|
+
* `DEFAULT_FAMILY_ORDER` through the catalog instead — see
|
|
16
|
+
* `ladderTargetForFamily`/`downgradeModel` in fallback.ts. Also the historic
|
|
17
|
+
* name of the override env (`OPENCODE_AUTH_LB_ANTHROPIC_OPUS_FALLBACK_MODEL`):
|
|
18
|
+
* set it to a model id to PIN the downgrade target (bypassing the ladder), or
|
|
19
|
+
* to an empty string to DISABLE downgrading (revert to cooling the whole
|
|
20
|
+
* account down).
|
|
21
|
+
*/
|
|
22
|
+
export declare const DEFAULT_OPUS_FALLBACK_MODEL = "claude-sonnet-4-6";
|
|
23
|
+
/**
|
|
24
|
+
* Model FAMILIES best → worst: the fallback ladder for a tier-capped request
|
|
25
|
+
* walks this order strictly BELOW the limited model's family and picks the
|
|
26
|
+
* highest-versioned catalog model of the first family that has one (e.g. a
|
|
27
|
+
* capped `claude-fable-5` prefers `claude-opus-4-9` over `claude-opus-4-8`;
|
|
28
|
+
* if the whole Opus tier is capped too, the next pass lands on Sonnet). A
|
|
29
|
+
* family NOT in this list (a future top tier) is treated as ABOVE the first
|
|
30
|
+
* entry — new premium tiers historically appear at the top. Overridable via
|
|
31
|
+
* `OPENCODE_AUTH_LB_ANTHROPIC_FAMILY_ORDER` (comma-separated, best first) so
|
|
32
|
+
* future models need a config tweak, not a code change.
|
|
33
|
+
*/
|
|
34
|
+
export declare const DEFAULT_FAMILY_ORDER: readonly string[];
|
|
35
|
+
/** 429 header naming which rate-limit window is the binding constraint. */
|
|
36
|
+
export declare const REPRESENTATIVE_CLAIM_HEADER = "anthropic-ratelimit-unified-representative-claim";
|
|
37
|
+
/**
|
|
38
|
+
* `representative-claim` values that name a MODEL-TIER window (a per-model
|
|
39
|
+
* weekly/5h cap, e.g. `seven_day_opus`, `seven_day_fable`) rather than an
|
|
40
|
+
* account-wide one (bare `five_hour` / `seven_day`). The capture group is the
|
|
41
|
+
* tier name — the key of `PoolAccount.modelCooldownsUntil`.
|
|
42
|
+
*/
|
|
43
|
+
export declare const MODEL_TIER_CLAIM_RE: RegExp;
|
|
44
|
+
/** 429 header with the unix-seconds reset time of the binding window. */
|
|
45
|
+
export declare const UNIFIED_RESET_HEADER = "anthropic-ratelimit-unified-reset";
|
|
46
|
+
export declare const REQUIRED_BETAS: string[];
|
|
47
|
+
export declare const OPENCODE_IDENTITY_PREFIX = "You are OpenCode";
|
|
48
|
+
export declare const CLAUDE_CODE_IDENTITY = "You are a Claude agent, built on Anthropic's Claude Agent SDK.";
|
|
49
|
+
export declare const CCH_SALT = "59cf53e54c78";
|
|
50
|
+
export declare const CCH_POSITIONS: number[];
|
|
51
|
+
export declare const CLAUDE_CODE_VERSION = "2.1.87";
|
|
52
|
+
export declare const CLAUDE_CODE_ENTRYPOINT = "sdk-cli";
|
|
53
|
+
/** User-Agent for /v1/messages (matches the reference plugin's validated value). */
|
|
54
|
+
export declare const USER_AGENT = "claude-cli/2.1.87 (external, cli)";
|
|
55
|
+
/**
|
|
56
|
+
* User-Agent for the /api/oauth/usage endpoint. This endpoint hard-rejects
|
|
57
|
+
* (429) requests whose UA is not `claude-code/<version>`.
|
|
58
|
+
*/
|
|
59
|
+
export declare const USAGE_USER_AGENT = "claude-code/2.1.87";
|
|
60
|
+
export declare const PARAGRAPH_REMOVAL_ANCHORS: string[];
|
|
61
|
+
export declare const TEXT_REPLACEMENTS: {
|
|
62
|
+
match: string;
|
|
63
|
+
replacement: string;
|
|
64
|
+
}[];
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ModelFallback, ReactiveModelFallback } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* The model FAMILY of a first-party model id — the tier name its per-model cap
|
|
4
|
+
* is keyed by: `claude-opus-4-7` → "opus", `claude-fable-5` → "fable",
|
|
5
|
+
* `claude-3-5-sonnet-latest` → "sonnet". The first alphabetic dash-segment
|
|
6
|
+
* after the vendor prefix; null when no such segment exists (unknown /
|
|
7
|
+
* non-first-party ids never participate in tier logic).
|
|
8
|
+
*/
|
|
9
|
+
export declare function modelFamily(model: string): string | null;
|
|
10
|
+
/**
|
|
11
|
+
* The tier a request body asks for (the `modelCooldownsUntil` key to consult
|
|
12
|
+
* before sending), or null when the body is not JSON / has no string model /
|
|
13
|
+
* the model has no recognizable family.
|
|
14
|
+
*/
|
|
15
|
+
export declare function requestModelTier(body: string): string | null;
|
|
16
|
+
export declare function resolveFamilyOrder(): readonly string[];
|
|
17
|
+
/**
|
|
18
|
+
* How the downgrade target is chosen:
|
|
19
|
+
* - `ladder` (env unset): walk the family order through the catalog.
|
|
20
|
+
* - `pinned` (env = a model id): always that model, bypassing the ladder.
|
|
21
|
+
* - `disabled` (env = empty/blank): no downgrade — account-wide cooldown.
|
|
22
|
+
*/
|
|
23
|
+
type FallbackSetting = {
|
|
24
|
+
kind: 'ladder';
|
|
25
|
+
} | {
|
|
26
|
+
kind: 'pinned';
|
|
27
|
+
model: string;
|
|
28
|
+
} | {
|
|
29
|
+
kind: 'disabled';
|
|
30
|
+
};
|
|
31
|
+
export declare function resolveFallbackSetting(): FallbackSetting;
|
|
32
|
+
/**
|
|
33
|
+
* Rewrite a request body's `model` one rung down the fallback ladder (or to
|
|
34
|
+
* the pinned override). Returns the rewritten body (+ the from/to ids for the
|
|
35
|
+
* toast) or null when: the feature is disabled, the body is not JSON, `model`
|
|
36
|
+
* is absent/non-string/family-less, or the chosen target lands in the SAME
|
|
37
|
+
* family (a same-tier rewrite cannot escape that tier's cap — e.g. a
|
|
38
|
+
* Sonnet-tier 429 with no lower catalog family has no cheaper first-party
|
|
39
|
+
* target, so it falls through to the normal account cooldown). Callers gate
|
|
40
|
+
* on a cheap condition (a stored tier cooldown or a response header) BEFORE
|
|
41
|
+
* this parse.
|
|
42
|
+
*/
|
|
43
|
+
export declare function downgradeModel(body: string, models: readonly string[]): ModelFallback | null;
|
|
44
|
+
/**
|
|
45
|
+
* REACTIVE: on a rejected response, when the binding limit is a MODEL-TIER cap
|
|
46
|
+
* (`representative-claim: seven_day_<tier>` / `five_hour_<tier>`) and the sent
|
|
47
|
+
* body's model can fall down the ladder, return the tier to persist, the tier
|
|
48
|
+
* reset, and the downgraded body (adopted only once the whole pool proves
|
|
49
|
+
* tier-limited). The header claim is checked BEFORE the body parse.
|
|
50
|
+
*/
|
|
51
|
+
export declare function planReactiveFallback(res: Response, body: string, now: number, models: readonly string[]): ReactiveModelFallback | null;
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { TokenSet } from '../../types';
|
|
2
|
+
import type { AuthorizeRequest } from '../types';
|
|
3
|
+
/** Begin the PKCE authorization flow (Claude Pro/Max subscription accounts). */
|
|
4
|
+
export declare function authorize(): Promise<AuthorizeRequest>;
|
|
5
|
+
/** Exchange a pasted authorization code/URL for tokens. Returns null on failure. */
|
|
6
|
+
export declare function exchange(input: string, verifier: string, redirectUri: string, expectedState?: string): Promise<TokenSet | null>;
|
|
7
|
+
/** Refresh an access token. Throws on failure; message includes the HTTP status. */
|
|
8
|
+
export declare function refresh(refreshToken: string): Promise<TokenSet>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type FetchInput } from '../types';
|
|
2
|
+
/** Merge incoming beta headers with the required OAuth betas, deduplicating. */
|
|
3
|
+
export declare function mergeBetaHeaders(headers: Headers): string;
|
|
4
|
+
/** Set OAuth auth + Claude Code headers; remove x-api-key (we use Bearer). */
|
|
5
|
+
export declare function setOAuthHeaders(headers: Headers, accessToken: string): void;
|
|
6
|
+
/**
|
|
7
|
+
* Strip the tool prefix from tool names in streaming response text.
|
|
8
|
+
*
|
|
9
|
+
* `alreadyScanned` marks a leading region of `text` that a PREVIOUS call
|
|
10
|
+
* already processed (createStrippedStream re-scans its retained tail together
|
|
11
|
+
* with each new chunk). A match ending inside that region can only be one
|
|
12
|
+
* RE-INTRODUCED by a prior replacement — a tool whose real name starts with
|
|
13
|
+
* `mcp_` (e.g. `mcp_Auth_lb_status` → stripped once to `"name": "mcp_..."`) —
|
|
14
|
+
* so it must be kept, not stripped a second time. A genuine pattern straddling
|
|
15
|
+
* the boundary ends AFTER `alreadyScanned` and is still replaced.
|
|
16
|
+
*/
|
|
17
|
+
export declare function stripToolPrefix(text: string, alreadyScanned?: number): string;
|
|
18
|
+
/** Add ?beta=true for /v1/messages, and honor ANTHROPIC_BASE_URL overrides. */
|
|
19
|
+
export declare function rewriteUrl(input: FetchInput): FetchInput;
|
|
20
|
+
/** Rewrite the request body: sanitize system prompt, inject billing header, prefix tools. */
|
|
21
|
+
export declare function rewriteRequestBody(body: string): string;
|
|
22
|
+
/** Wrap a streaming response so tool prefixes are stripped from emitted text. */
|
|
23
|
+
export declare function createStrippedStream(response: Response): Response;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PoolAccount, UsageSnapshot } from '../../types';
|
|
2
|
+
/**
|
|
3
|
+
* Parse usage from /v1/messages response headers (free, on every response).
|
|
4
|
+
* The parse itself (null-short-circuit, "no capturedAt", "`{}` collapses to
|
|
5
|
+
* null") lives in the shared `parseWindowPairHeaders` — only the header names
|
|
6
|
+
* and the fraction scale are Anthropic-specific.
|
|
7
|
+
*/
|
|
8
|
+
export declare function parseUsageHeaders(headers: Headers): Partial<UsageSnapshot> | null;
|
|
9
|
+
/**
|
|
10
|
+
* Poll the dedicated usage endpoint for authoritative 5h + 7d utilization without
|
|
11
|
+
* consuming inference quota. Returns null on any failure — including a 200 whose
|
|
12
|
+
* body is not the usage shape — so the caller keeps the last-known snapshot.
|
|
13
|
+
*/
|
|
14
|
+
export declare function fetchUsage(account: PoolAccount, now: number): Promise<UsageSnapshot | null>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FetchInput } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Merge headers from a Request object and/or a RequestInit headers value into a
|
|
4
|
+
* single Headers instance. Provider-agnostic; used by the core fetch before
|
|
5
|
+
* handing the Headers to an adapter's applyAuth.
|
|
6
|
+
*/
|
|
7
|
+
export declare function mergeHeaders(input: FetchInput, init?: RequestInit): Headers;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP timeout budgets shared by BOTH provider adapters. These are not
|
|
3
|
+
* provider-specific values that happen to coincide — the invariants they
|
|
4
|
+
* encode (stay below the refresh lock's stale window; bound fire-and-forget
|
|
5
|
+
* usage polls) apply identically to the Anthropic and OpenAI paths, so they
|
|
6
|
+
* are single-sourced here (like `pkce.ts` / `oauth-callback.ts` /
|
|
7
|
+
* `usage-http.ts`) and re-exported from each provider's `constants.ts`.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Bound every OAuth token-endpoint call (exchange/refresh) so a hung network never
|
|
11
|
+
* holds the per-account refresh lock for its full stale window. Must stay below the
|
|
12
|
+
* refresh lock's stale timeout (120s) so the lock is released by the abort first.
|
|
13
|
+
*/
|
|
14
|
+
export declare const OAUTH_HTTP_TIMEOUT_MS = 30000;
|
|
15
|
+
/**
|
|
16
|
+
* Bound every usage-endpoint poll so a hung usage server never accumulates
|
|
17
|
+
* fire-and-forget sockets. `fetchUsage` is called fire-and-forget from
|
|
18
|
+
* `refreshUsageInBackground`; the `lastPoll` throttle prevents same-account
|
|
19
|
+
* re-poll within SEED_TTL_MS but does NOT cancel an in-flight hung fetch.
|
|
20
|
+
* Symmetric with `OAUTH_HTTP_TIMEOUT_MS` (30 s).
|
|
21
|
+
*/
|
|
22
|
+
export declare const USAGE_HTTP_TIMEOUT_MS = 30000;
|
|
23
|
+
/**
|
|
24
|
+
* Upper bound for any quota-reset-related duration derived from a response
|
|
25
|
+
* header (a `Retry-After` cooldown in `fetch.ts`, a tier `unified-reset` in
|
|
26
|
+
* `providers/anthropic/fallback.ts`). Both encode the same invariant: a real
|
|
27
|
+
* quota window is at most weekly (≤ 7 days), so anything past this bound is a
|
|
28
|
+
* broken server/proxy clock and must be rejected — falling through to the
|
|
29
|
+
* caller's short self-healing default — instead of sidelining an account (or
|
|
30
|
+
* downgrading a model tier) for years on a bogus far-future value.
|
|
31
|
+
*/
|
|
32
|
+
export declare const MAX_QUOTA_RESET_BOUND_MS: number;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared OAuth callback helpers used by both the Anthropic and OpenAI OAuth
|
|
3
|
+
* flows. Sibling to `pkce.ts` — both providers had near-identical private
|
|
4
|
+
* copies of `generateState` (byte-identical) and `parseCallbackInput`
|
|
5
|
+
* (95%-identical: the Anthropic variant also accepts the legacy `code#state`
|
|
6
|
+
* hash-split format pasted manually by users). Keeping two copies meant every
|
|
7
|
+
* future tweak (new callback format, whitespace normalization) had to be made
|
|
8
|
+
* in two places to stay consistent. `readTokenResponse` unifies the same way:
|
|
9
|
+
* the token-endpoint 200-body validation was copy-pasted four times (exchange
|
|
10
|
+
* + refresh, per provider). `toTokenSet` single-sources the response→TokenSet
|
|
11
|
+
* mapping contract the same way — it too was maintained as two mirrored
|
|
12
|
+
* private copies.
|
|
13
|
+
*/
|
|
14
|
+
import type { TokenSet } from '../types';
|
|
15
|
+
/** The fields every provider's token-endpoint response must carry. */
|
|
16
|
+
export interface BaseTokenResponse {
|
|
17
|
+
access_token: string;
|
|
18
|
+
refresh_token?: string;
|
|
19
|
+
expires_in: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Map a token-endpoint response body to a TokenSet. RFC 6749 §5.1: the server
|
|
23
|
+
* MAY omit refresh_token; fall back to `previousRefresh` then — `''` at
|
|
24
|
+
* exchange time (no previous token; a missing field still fails the next
|
|
25
|
+
* refresh loudly, but never writes `undefined` onto the pool account), the
|
|
26
|
+
* current token at refresh time. Providers with extra fields (e.g. OpenAI's
|
|
27
|
+
* `accountId`) spread this result and add them.
|
|
28
|
+
*/
|
|
29
|
+
export declare function toTokenSet(json: BaseTokenResponse, previousRefresh: string): TokenSet;
|
|
30
|
+
/**
|
|
31
|
+
* Parse and validate an OAuth token-endpoint 200 body. Returns null when the
|
|
32
|
+
* body is not JSON, `access_token` is missing/empty, `expires_in` is
|
|
33
|
+
* missing, or a PRESENT, non-null `refresh_token` is non-string — otherwise
|
|
34
|
+
* a SyntaxError escapes into the login flow, an empty-string `access_token`
|
|
35
|
+
* writes `access: ''` onto the pool row (silently bricking auth until the
|
|
36
|
+
* next self-healing refresh), a missing `expires_in` poisons the pool with
|
|
37
|
+
* `expires: NaN` (which `needsRefresh` never treats as stale), or a
|
|
38
|
+
* malformed `refresh_token` (e.g. a number) writes a non-string value onto
|
|
39
|
+
* `PoolAccount.refresh` (typed `string`) via `toTokenSet`'s
|
|
40
|
+
* `json.refresh_token || previousRefresh` — until the next `readPool()`
|
|
41
|
+
* heals it via `normalizeAccounts`'s own `typeof row.refresh !== 'string'`
|
|
42
|
+
* guard (`pool/store.ts`). `refresh_token` is still OPTIONAL per RFC 6749
|
|
43
|
+
* §5.1 — only a present-but-wrong-type value is rejected. An explicit JSON
|
|
44
|
+
* `null` (how some statically-typed token endpoints serialize an omitted
|
|
45
|
+
* `Option<String>`) is treated the same as `undefined`: both are falsy, so
|
|
46
|
+
* `toTokenSet`'s `json.refresh_token || previousRefresh` already falls back
|
|
47
|
+
* identically for either — rejecting `null` here would just misreport a
|
|
48
|
+
* valid response as "malformed" for no behavioral gain.
|
|
49
|
+
* `expires_in` must be FINITE, not just a number: `JSON.parse('1e999')`
|
|
50
|
+
* legally yields `Infinity`, which would poison the pool with
|
|
51
|
+
* `expires: Infinity` — the same never-stale soft-brick as NaN.
|
|
52
|
+
* It must also be POSITIVE: RFC 6749 §5.1 defines it as a lifetime in
|
|
53
|
+
* seconds, so 0/negative is nonsensical — and would write an already-expired
|
|
54
|
+
* `expires` to the pool, making `needsRefresh` true on EVERY request (a
|
|
55
|
+
* network refresh round-trip per request, each burning a single-use rotated
|
|
56
|
+
* refresh token).
|
|
57
|
+
* Its MS PRODUCT must be finite too: a finite-but-huge value like `1e306`
|
|
58
|
+
* passes the checks above, yet every consumer computes
|
|
59
|
+
* `Date.now() + expires_in * 1000`, and `1e306 * 1000` collapses to
|
|
60
|
+
* `+Infinity` — the same never-stale soft-brick. Mirrors the `secondsToMs`
|
|
61
|
+
* guard in `src/util.ts` and `cooldownUntilFrom`'s retry-after guard in
|
|
62
|
+
* `src/fetch.ts`.
|
|
63
|
+
* Callers decide the failure shape: exchange sites return null, refresh sites
|
|
64
|
+
* throw their status-prefixed "malformed token response body" error.
|
|
65
|
+
*/
|
|
66
|
+
export declare function readTokenResponse<T extends BaseTokenResponse = BaseTokenResponse>(res: Response): Promise<T | null>;
|
|
67
|
+
/**
|
|
68
|
+
* Validate an exchange-endpoint response with the shared exchange failure
|
|
69
|
+
* contract: null on a non-ok status (with the body stream — and its HTTP
|
|
70
|
+
* connection — released), null on a malformed 200 body (see
|
|
71
|
+
* readTokenResponse). Refresh-side sibling of `readRefreshResponse`; both
|
|
72
|
+
* providers' `exchange` delegate here so a future tweak (logging, a new
|
|
73
|
+
* failure shape) can never be made in only one copy.
|
|
74
|
+
*/
|
|
75
|
+
export declare function readExchangeResponse<T extends BaseTokenResponse = BaseTokenResponse>(res: Response): Promise<T | null>;
|
|
76
|
+
/**
|
|
77
|
+
* Validate a refresh-endpoint response, throwing the status-prefixed error
|
|
78
|
+
* `isInvalidGrant()` (src/refresh.ts) parses — that message format is a
|
|
79
|
+
* contract, single-sourced here so it can never be half-updated. A malformed
|
|
80
|
+
* 200 body must never reach commitRefresh, or the account gets `expires: NaN`
|
|
81
|
+
* (which needsRefresh never treats as stale) and soft-bricks into a perpetual
|
|
82
|
+
* auth-cooldown loop that survives restarts; the 200-status prefix keeps
|
|
83
|
+
* isInvalidGrant() false (200 ≠ 400/401), so the failure stays transient —
|
|
84
|
+
* the account is NOT disabled.
|
|
85
|
+
*/
|
|
86
|
+
export declare function readRefreshResponse<T extends BaseTokenResponse = BaseTokenResponse>(res: Response): Promise<T>;
|
|
87
|
+
/** Generate an opaque OAuth `state` parameter (32-char hex, no hyphens). */
|
|
88
|
+
export declare function generateState(): string;
|
|
89
|
+
/**
|
|
90
|
+
* Parse a pasted OAuth callback into `{ code, state }`. Accepts a full
|
|
91
|
+
* `https://…?code=…&state=…` URL, a `key=value` form (`code=…&state=…`), and
|
|
92
|
+
* — when `allowHashFormat` is on — the legacy/manual `<code>#<state>` format.
|
|
93
|
+
* Returns null when no shape matches.
|
|
94
|
+
*/
|
|
95
|
+
export declare function parseCallbackInput(input: string, options?: {
|
|
96
|
+
allowHashFormat?: boolean;
|
|
97
|
+
}): {
|
|
98
|
+
code: string;
|
|
99
|
+
state: string;
|
|
100
|
+
} | null;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ProviderAdapter } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* OpenAI ChatGPT/Codex (subscription OAuth) provider adapter.
|
|
4
|
+
*
|
|
5
|
+
* Assumes opencode's `openai` provider is configured to use the Responses API
|
|
6
|
+
* (the standard ChatGPT/Codex setup) — requests on a `/responses` path are routed
|
|
7
|
+
* to the Codex backend. Other paths are left untouched.
|
|
8
|
+
*/
|
|
9
|
+
export declare const openaiAdapter: ProviderAdapter;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const PROVIDER_ID = "openai";
|
|
2
|
+
export declare const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
3
|
+
export declare const AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize";
|
|
4
|
+
export declare const TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
5
|
+
export declare const REDIRECT_URI = "http://localhost:1455/auth/callback";
|
|
6
|
+
export { OAUTH_HTTP_TIMEOUT_MS, USAGE_HTTP_TIMEOUT_MS } from '../http-timeouts';
|
|
7
|
+
export declare const OAUTH_SCOPES: string[];
|
|
8
|
+
/** ChatGPT-OAuth Codex inference endpoint. */
|
|
9
|
+
export declare const CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
|
|
10
|
+
/** Dedicated usage endpoint for ChatGPT OAuth (no inference required). */
|
|
11
|
+
export declare const USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
12
|
+
/** User-Agent for the usage endpoint — the Codex CLI sends `codex-cli`. */
|
|
13
|
+
export declare const USAGE_USER_AGENT = "codex-cli";
|
|
14
|
+
export declare const ORIGINATOR = "opencode";
|
|
15
|
+
export declare const OPENAI_BETA = "responses=experimental";
|
|
16
|
+
export declare const USER_AGENT: string;
|
|
17
|
+
/** Stable per-process session id sent as the `session-id` header. */
|
|
18
|
+
export declare const SESSION_ID: `${string}-${string}-${string}-${string}-${string}`;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { PoolAccount } from '../../types';
|
|
2
|
+
/** Extract the ChatGPT account id from an id_token, checking all known claim paths. */
|
|
3
|
+
export declare function extractAccountId(idToken: string): string | undefined;
|
|
4
|
+
/** Stored ChatGPT account id, or one decoded from the access-token JWT. */
|
|
5
|
+
export declare function resolveAccountId(account: PoolAccount): string | undefined;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TokenSet } from '../../types';
|
|
2
|
+
import type { AuthorizeRequest } from '../types';
|
|
3
|
+
/** Begin the ChatGPT PKCE authorization flow. */
|
|
4
|
+
export declare function authorize(): Promise<AuthorizeRequest>;
|
|
5
|
+
/**
|
|
6
|
+
* Exchange a pasted callback URL (or `code#state`) for tokens. Returns null on
|
|
7
|
+
* failure. The redirect lands on http://localhost:1455/auth/callback — the user
|
|
8
|
+
* copies that URL from the browser address bar and pastes it here.
|
|
9
|
+
*/
|
|
10
|
+
export declare function exchange(input: string, verifier: string, redirectUri: string, expectedState?: string): Promise<TokenSet | null>;
|
|
11
|
+
/** Refresh an access token. Throws on failure; message includes the HTTP status. */
|
|
12
|
+
export declare function refresh(refreshToken: string): Promise<TokenSet>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { PoolAccount } from '../../types';
|
|
2
|
+
import { type FetchInput } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Codex (ChatGPT-OAuth) request shaping, distilled from the openai/codex CLI,
|
|
5
|
+
* anomalyco/opencode's codex plugin, and the openresponses Go middleware.
|
|
6
|
+
*/
|
|
7
|
+
/** Set OAuth auth + Codex headers; drop the SDK's dummy key/x-api-key. */
|
|
8
|
+
export declare function applyAuth(headers: Headers, account: PoolAccount): void;
|
|
9
|
+
/** Rewrite Responses-API requests to the Codex backend; leave others untouched. */
|
|
10
|
+
export declare function rewriteUrl(input: FetchInput): FetchInput;
|
|
11
|
+
/**
|
|
12
|
+
* Apply the mandatory Codex backend body quirks:
|
|
13
|
+
* - store: false (backend rejects true)
|
|
14
|
+
* - include += reasoning.encrypted_content (preserve CoT across turns)
|
|
15
|
+
* - non-empty instructions
|
|
16
|
+
* - drop max_output_tokens (match codex cli)
|
|
17
|
+
*/
|
|
18
|
+
export declare function rewriteRequestBody(body: string): string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { PoolAccount, UsageSnapshot } from '../../types';
|
|
2
|
+
/**
|
|
3
|
+
* Parse usage from Codex response headers (x-codex-{primary,secondary}-*).
|
|
4
|
+
* primary = ~5h window (hourly), secondary = weekly window. The parse itself
|
|
5
|
+
* (null-short-circuit, "no capturedAt", "`{}` collapses to null") lives in the
|
|
6
|
+
* shared `parseWindowPairHeaders` — only the header names and the percent
|
|
7
|
+
* scale are Codex-specific.
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseUsageHeaders(headers: Headers): Partial<UsageSnapshot> | null;
|
|
10
|
+
/**
|
|
11
|
+
* Poll GET /wham/usage for authoritative 5h (primary) + weekly (secondary)
|
|
12
|
+
* utilization without consuming inference quota. Null on failure. Sends the Codex
|
|
13
|
+
* CLI's `codex-cli` UA and the `chatgpt-account-id` (from the stored id or, as a
|
|
14
|
+
* fallback, decoded from the access-token JWT) — required for team/workspace accounts.
|
|
15
|
+
*/
|
|
16
|
+
export declare function fetchUsage(account: PoolAccount, now: number): Promise<UsageSnapshot | null>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared PKCE (RFC 7636) helper used by both the Anthropic and OpenAI OAuth
|
|
3
|
+
* flows (the per-provider copies were byte-identical). Only the S256 method is
|
|
4
|
+
* supported; both consumers hard-code `code_challenge_method=S256` into their
|
|
5
|
+
* URL params, so the method is not part of the return shape.
|
|
6
|
+
*/
|
|
7
|
+
export declare function generatePKCE(): Promise<{
|
|
8
|
+
verifier: string;
|
|
9
|
+
challenge: string;
|
|
10
|
+
}>;
|