@omnicross/subscriptions 0.3.1 → 0.4.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/dist/oauth.d.cts CHANGED
@@ -2,6 +2,171 @@ import { FetchLike } from '@omnicross/core/auth/GeminiCodeAssistProjectResolver'
2
2
  export { FetchLike } from '@omnicross/core/auth/GeminiCodeAssistProjectResolver';
3
3
  import { TokenExchangeRequest, OAuthParams } from '@omnicross/contracts/account-tokens-types';
4
4
 
5
+ /**
6
+ * GitHub Copilot OAuth flow — RFC 8628 device grant against the official
7
+ * Copilot CLI app at `github.com` (scope `read:user`).
8
+ *
9
+ * The minted `ghu_` token is LONG-LIVED: refresh is a LOCAL no-op (access and
10
+ * refresh are the same token; a far-future expiry keeps every generic
11
+ * near-expiry path idle). After the user approves, the flow (a) reads the
12
+ * GitHub identity (`/user` → login + email), (b) discovers the plan-advertised
13
+ * Copilot API endpoint (`api.github.com/copilot_internal/user` →
14
+ * `endpoints.api`), and (c) best-effort sweeps the model roster's
15
+ * `POST /models/{id}/policy {"state":"enabled"}` — the Claude/Grok models are
16
+ * policy-gated until enabled.
17
+ *
18
+ * Every Copilot API request carries the mirrored Copilot CLI identity
19
+ * (`copilot/1.0.82` UA + Editor-Version, Copilot-Integration-Id, Copilot-
20
+ * Harness-Id, Openai-Intent) plus `X-GitHub-Api-Version: 2026-08-01` — without
21
+ * the version header the endpoint serves default-tier context limits only
22
+ * (e.g. 264k instead of 1M). NEVER send the API version header to
23
+ * `api.github.com` REST endpoints (they validate it against the REST
24
+ * vocabulary). Relay traffic is agent-initiated CLI traffic, so the per-request
25
+ * dynamic `X-Initiator: agent` / `X-Interaction-Type: conversation-agent`
26
+ * pair is STATIC here — the same classification the official CLI sends, and
27
+ * the one GitHub bills at a 0 premium multiplier.
28
+ *
29
+ * GitHub Enterprise domains ride the SAME device flow on the enterprise host
30
+ * (`https://<domain>/login/{device/code,oauth/access_token}`); the stored
31
+ * `enterpriseUrl` additionally routes inference through `copilot-api.<domain>`
32
+ * and the REST probes (identity/quota) through `api.<domain>`.
33
+ *
34
+ * @module @omnicross/subscriptions/oauth/flows/copilot
35
+ */
36
+
37
+ /** The official Copilot CLI OAuth app (public client). */
38
+ declare const COPILOT_OAUTH_CONFIG: {
39
+ readonly clientId: "Ov23ctDVkRmgkPke0Mmm";
40
+ readonly scope: "read:user";
41
+ readonly deviceEndpoint: "https://github.com/login/device/code";
42
+ readonly tokenEndpoint: "https://github.com/login/oauth/access_token";
43
+ };
44
+ /**
45
+ * Normalize an operator-supplied GitHub Enterprise domain: accept a bare host
46
+ * or a full URL and keep ONLY the lowercase hostname. Empty input and the
47
+ * public github.com hosts mean the personal flow (`undefined`); anything that
48
+ * does not parse as a host THROWS (the caller surfaces the message).
49
+ */
50
+ declare function normalizeCopilotEnterpriseDomain(input: string | undefined): string | undefined;
51
+ /**
52
+ * Device-flow endpoints for a login — the GHE host, or the github.com default
53
+ * when `enterpriseUrl` is absent/normalized-away.
54
+ */
55
+ declare function copilotOAuthUrls(enterpriseUrl?: string): {
56
+ deviceEndpoint: string;
57
+ tokenEndpoint: string;
58
+ };
59
+ /** GitHub's device-poll pacing: each wait scales ×1.2 (×1.4 after slow_down). */
60
+ declare const COPILOT_POLL_INITIAL_MULTIPLIER = 1.2;
61
+ declare const COPILOT_POLL_SLOW_DOWN_MULTIPLIER = 1.4;
62
+ /** Headers for `api.github.com` REST/user endpoints (NO Copilot API version). */
63
+ declare const COPILOT_GITHUB_HEADERS: Record<string, string>;
64
+ /**
65
+ * The STATIC Copilot API identity every inference request carries
66
+ * (mirror of the official CLI's request layer).
67
+ */
68
+ declare const COPILOT_API_HEADERS: Record<string, string>;
69
+ /** Far-future expiry (10 years) — ghu_ tokens have no refresh lifecycle. */
70
+ declare const COPILOT_FAR_FUTURE_MS: number;
71
+ /** Device-authorization response (RFC 8628 §3.2). */
72
+ interface CopilotDeviceAuthorization {
73
+ userCode: string;
74
+ deviceCode: string;
75
+ verificationUri: string;
76
+ /** Poll interval in seconds. */
77
+ interval: number;
78
+ /** Lifetime in seconds. */
79
+ expiresIn: number;
80
+ }
81
+ /** One polled token attempt's outcome. */
82
+ type CopilotDevicePoll = {
83
+ state: 'pending';
84
+ } | {
85
+ state: 'slowDown';
86
+ intervalSeconds?: number;
87
+ } | {
88
+ state: 'done';
89
+ accessToken: string;
90
+ } | {
91
+ state: 'failed';
92
+ message: string;
93
+ };
94
+ /** Request a device code the user approves at `verification_uri`. */
95
+ declare function requestCopilotDeviceAuthorization(fetchImpl: FetchLike, enterpriseUrl?: string): Promise<CopilotDeviceAuthorization>;
96
+ /** Poll the token endpoint ONCE (RFC 8628 §3.5 semantics). */
97
+ declare function pollCopilotDeviceToken(deviceCode: string, fetchImpl: FetchLike, enterpriseUrl?: string): Promise<CopilotDevicePoll>;
98
+ /**
99
+ * Drive the device-code login to completion. GitHub's pacing multiplies each
100
+ * wait (×1.2 baseline, ×1.4 once a slow_down arrives) — deliberately more
101
+ * conservative than the RFC's flat +5s. `onPending` fires after each poll.
102
+ */
103
+ declare function awaitCopilotDeviceToken(authorization: CopilotDeviceAuthorization, fetchImpl: FetchLike, options?: {
104
+ deadlineMs?: number;
105
+ sleep?: (ms: number) => Promise<void>;
106
+ onPending?: () => void;
107
+ /** GHE host — routes the token poll off github.com. */
108
+ enterpriseUrl?: string;
109
+ }): Promise<{
110
+ accessToken: string;
111
+ }>;
112
+ /** The GitHub identity for the minted token (login = the account id). */
113
+ declare function fetchCopilotIdentity(accessToken: string, fetchImpl: FetchLike, enterpriseUrl?: string): Promise<{
114
+ accountId?: string;
115
+ email?: string;
116
+ }>;
117
+ /**
118
+ * Resolve the plan-advertised Copilot API endpoint
119
+ * (`copilot_internal/user` → `endpoints.api`); `undefined` falls back to the
120
+ * canonical personal host at dispatch time.
121
+ */
122
+ declare function discoverCopilotApiEndpoint(accessToken: string, fetchImpl: FetchLike, enterpriseUrl?: string): Promise<string | undefined>;
123
+ /**
124
+ * Enable one policy-gated model (`POST /models/{id}/policy`). Best-effort —
125
+ * the response is ignored and failures never fail the login.
126
+ */
127
+ declare function enableCopilotModel(accessToken: string, modelId: string, baseUrl: string, fetchImpl: FetchLike): Promise<boolean>;
128
+ /**
129
+ * Sweep the model roster's policy-enable endpoint (batched ×5). Best-effort:
130
+ * every failure is swallowed (already-enabled models return non-2xx too).
131
+ */
132
+ declare function enableAllCopilotModels(accessToken: string, config: {
133
+ apiEndpoint?: string;
134
+ enterpriseUrl?: string;
135
+ } | undefined, fetchImpl: FetchLike, onProgress?: (modelId: string, ok: boolean) => void): Promise<void>;
136
+ /**
137
+ * "Refresh" a Copilot token — a LOCAL no-op. GitHub OAuth device tokens are
138
+ * long-lived with no exchange endpoint; the stored access token IS the
139
+ * credential. Returns the same pair with a far-future expiry so every generic
140
+ * refresh path stays a no-network success.
141
+ */
142
+ declare function refreshCopilotToken(accessToken: string): {
143
+ accessToken: string;
144
+ refreshToken: string;
145
+ expiresIn: number;
146
+ };
147
+
148
+ declare const copilot_COPILOT_API_HEADERS: typeof COPILOT_API_HEADERS;
149
+ declare const copilot_COPILOT_FAR_FUTURE_MS: typeof COPILOT_FAR_FUTURE_MS;
150
+ declare const copilot_COPILOT_GITHUB_HEADERS: typeof COPILOT_GITHUB_HEADERS;
151
+ declare const copilot_COPILOT_OAUTH_CONFIG: typeof COPILOT_OAUTH_CONFIG;
152
+ declare const copilot_COPILOT_POLL_INITIAL_MULTIPLIER: typeof COPILOT_POLL_INITIAL_MULTIPLIER;
153
+ declare const copilot_COPILOT_POLL_SLOW_DOWN_MULTIPLIER: typeof COPILOT_POLL_SLOW_DOWN_MULTIPLIER;
154
+ type copilot_CopilotDeviceAuthorization = CopilotDeviceAuthorization;
155
+ type copilot_CopilotDevicePoll = CopilotDevicePoll;
156
+ declare const copilot_awaitCopilotDeviceToken: typeof awaitCopilotDeviceToken;
157
+ declare const copilot_copilotOAuthUrls: typeof copilotOAuthUrls;
158
+ declare const copilot_discoverCopilotApiEndpoint: typeof discoverCopilotApiEndpoint;
159
+ declare const copilot_enableAllCopilotModels: typeof enableAllCopilotModels;
160
+ declare const copilot_enableCopilotModel: typeof enableCopilotModel;
161
+ declare const copilot_fetchCopilotIdentity: typeof fetchCopilotIdentity;
162
+ declare const copilot_normalizeCopilotEnterpriseDomain: typeof normalizeCopilotEnterpriseDomain;
163
+ declare const copilot_pollCopilotDeviceToken: typeof pollCopilotDeviceToken;
164
+ declare const copilot_refreshCopilotToken: typeof refreshCopilotToken;
165
+ declare const copilot_requestCopilotDeviceAuthorization: typeof requestCopilotDeviceAuthorization;
166
+ declare namespace copilot {
167
+ export { copilot_COPILOT_API_HEADERS as COPILOT_API_HEADERS, copilot_COPILOT_FAR_FUTURE_MS as COPILOT_FAR_FUTURE_MS, copilot_COPILOT_GITHUB_HEADERS as COPILOT_GITHUB_HEADERS, copilot_COPILOT_OAUTH_CONFIG as COPILOT_OAUTH_CONFIG, copilot_COPILOT_POLL_INITIAL_MULTIPLIER as COPILOT_POLL_INITIAL_MULTIPLIER, copilot_COPILOT_POLL_SLOW_DOWN_MULTIPLIER as COPILOT_POLL_SLOW_DOWN_MULTIPLIER, type copilot_CopilotDeviceAuthorization as CopilotDeviceAuthorization, type copilot_CopilotDevicePoll as CopilotDevicePoll, copilot_awaitCopilotDeviceToken as awaitCopilotDeviceToken, copilot_copilotOAuthUrls as copilotOAuthUrls, copilot_discoverCopilotApiEndpoint as discoverCopilotApiEndpoint, copilot_enableAllCopilotModels as enableAllCopilotModels, copilot_enableCopilotModel as enableCopilotModel, copilot_fetchCopilotIdentity as fetchCopilotIdentity, copilot_normalizeCopilotEnterpriseDomain as normalizeCopilotEnterpriseDomain, copilot_pollCopilotDeviceToken as pollCopilotDeviceToken, copilot_refreshCopilotToken as refreshCopilotToken, copilot_requestCopilotDeviceAuthorization as requestCopilotDeviceAuthorization };
168
+ }
169
+
5
170
  /**
6
171
  * Kimi Code OAuth flow — RFC 8628 device authorization grant (host-clean).
7
172
  *
@@ -240,4 +405,132 @@ declare namespace gemini {
240
405
  export { gemini_exchangeCodeForTokens as exchangeCodeForTokens, gemini_generateAuthParams as generateAuthParams, gemini_refreshAccessToken as refreshAccessToken };
241
406
  }
242
407
 
243
- export { claude as claudeOAuth, codex as codexOAuth, gemini as geminiOAuth, kimiFingerprintHeaders, kimi as kimiOAuth };
408
+ /**
409
+ * Grok (xAI SuperGrok) OAuth flow — RFC 8628 device authorization grant.
410
+ *
411
+ * Mirrors the public Grok CLI client at `auth.x.ai` (client id reverse-derived
412
+ * from the official CLI, same as the audit source): a form-encoded
413
+ * device-authorization request with the CLI scopes, the user approving at
414
+ * `verification_uri`, and a polled token request whose RFC error codes arrive
415
+ * as HTTP 400 JSON bodies (hence the raw-form POST helper — the shared
416
+ * `postForm` rejects on any `error` body). Refresh is a standard
417
+ * `refresh_token` grant on the same endpoint.
418
+ *
419
+ * The token endpoint is NOT hard-coded: it is resolved through xAI's OIDC
420
+ * discovery document and pinned to HTTPS `x.ai` / `*.x.ai` (the discovery
421
+ * response is long-lived and its endpoint receives every future refresh
422
+ * token, so a drifted document must not redirect credentials off-origin).
423
+ * The discovery result is cached process-wide for an hour.
424
+ *
425
+ * The access token is a JWT; its `sub` claim is the account id. Inference
426
+ * rides `api.x.ai/v1/responses` (the codex-style Responses wire); the weekly
427
+ * credits / unified monthly quota lives behind `cli-chat-proxy.grok.com/v1/
428
+ * billing` (see `GrokAllowanceCollector`).
429
+ *
430
+ * Escape hatches: `GROK_OAUTH_DEVICE_ENDPOINT` / `GROK_OAUTH_DISCOVERY_URL`
431
+ * override the two endpoints (host-pinning still applies to the discovery
432
+ * result).
433
+ *
434
+ * @module @omnicross/subscriptions/oauth/flows/grok
435
+ */
436
+
437
+ /** Grok CLI OAuth configuration (public client, mirrors the official CLI). */
438
+ declare const GROK_OAUTH_CONFIG: {
439
+ readonly clientId: "b1a00492-073a-47ea-816f-4c329264a828";
440
+ readonly deviceAuthorizationEndpoint: string;
441
+ readonly discoveryUrl: string;
442
+ /** The CLI's full scope set — the token must carry `grok-cli:access` for inference. */
443
+ readonly scopes: readonly ["openid", "profile", "email", "offline_access", "grok-cli:access", "api:access"];
444
+ };
445
+ /** Device-authorization response (RFC 8628 §3.2). */
446
+ interface GrokDeviceAuthorization {
447
+ userCode: string;
448
+ deviceCode: string;
449
+ /** Preferred: pre-fills the code when opened in the user's browser. */
450
+ verificationUri: string;
451
+ verificationUriComplete?: string;
452
+ /** Poll interval in seconds (RFC default 5). */
453
+ interval?: number;
454
+ /** Lifetime in seconds. */
455
+ expiresIn?: number;
456
+ }
457
+ /** One polled token attempt's outcome. */
458
+ type GrokDevicePoll = {
459
+ state: 'pending';
460
+ intervalSeconds?: number;
461
+ } | {
462
+ state: 'done';
463
+ accessToken: string;
464
+ refreshToken: string;
465
+ expiresIn: number;
466
+ } | {
467
+ state: 'failed';
468
+ message: string;
469
+ };
470
+ /** `x.ai` or any `*.x.ai` host — the only origins a token endpoint may live on. */
471
+ declare function isGrokAuthHostname(host: string): boolean;
472
+ /**
473
+ * Validate an endpoint URL against the token-endpoint contract: HTTPS and an
474
+ * `x.ai` / `*.x.ai` host. Throws a descriptive error otherwise.
475
+ */
476
+ declare function validateGrokAuthEndpoint(url: string, field: string): string;
477
+ /** Test seam: drop the discovery cache (the cache is process-level). */
478
+ declare function resetGrokDiscoveryCache(): void;
479
+ /**
480
+ * Resolve the OIDC token endpoint via discovery (cached 1h). The document's
481
+ * `token_endpoint` is host-pinned so a compromised or drifted discovery
482
+ * response can never redirect credentials off the xAI origin.
483
+ */
484
+ declare function resolveGrokTokenEndpoint(fetchImpl: FetchLike, timeoutMs?: number): Promise<string>;
485
+ /** Request a device code the user approves at `verification_uri`. */
486
+ declare function requestGrokDeviceAuthorization(fetchImpl: FetchLike): Promise<GrokDeviceAuthorization>;
487
+ /**
488
+ * Poll the token endpoint ONCE. RFC 8628 §3.5 semantics: `authorization_pending`
489
+ * keeps polling, `slow_down` adds 5s to the interval, anything else fails.
490
+ */
491
+ declare function pollGrokDeviceToken(deviceCode: string, tokenEndpoint: string, fetchImpl: FetchLike): Promise<GrokDevicePoll>;
492
+ /**
493
+ * Drive the device-code login to completion: poll at the device flow's
494
+ * interval (`slow_down` +5s each time, applied BEFORE the next wait) until
495
+ * done/expired/denied or `deadlineMs` elapses. `onPending` fires after each
496
+ * pending poll (so a CLI can render a spinner).
497
+ */
498
+ declare function awaitGrokDeviceToken(authorization: GrokDeviceAuthorization, tokenEndpoint: string, fetchImpl: FetchLike, options?: {
499
+ intervalMs?: number;
500
+ deadlineMs?: number;
501
+ sleep?: (ms: number) => Promise<void>;
502
+ onPending?: () => void;
503
+ }): Promise<{
504
+ accessToken: string;
505
+ refreshToken: string;
506
+ expiresIn: number;
507
+ }>;
508
+ /** Refresh the access token with a `refresh_token` grant. */
509
+ declare function refreshGrokAccessToken(refreshToken: string, tokenEndpoint: string, fetchImpl: FetchLike): Promise<{
510
+ accessToken: string;
511
+ refreshToken: string;
512
+ expiresIn: number;
513
+ }>;
514
+ /**
515
+ * Decode the access-token JWT's `sub` claim (no verification — the issuer is
516
+ * trusted; we only read an id).
517
+ */
518
+ declare function grokAccountIdFromAccessToken(accessToken: string): string | undefined;
519
+
520
+ declare const grok_GROK_OAUTH_CONFIG: typeof GROK_OAUTH_CONFIG;
521
+ type grok_GrokDeviceAuthorization = GrokDeviceAuthorization;
522
+ type grok_GrokDevicePoll = GrokDevicePoll;
523
+ declare const grok_awaitGrokDeviceToken: typeof awaitGrokDeviceToken;
524
+ declare const grok_grokAccountIdFromAccessToken: typeof grokAccountIdFromAccessToken;
525
+ declare const grok_isGrokAuthHostname: typeof isGrokAuthHostname;
526
+ declare const grok_pollGrokDeviceToken: typeof pollGrokDeviceToken;
527
+ declare const grok_refreshGrokAccessToken: typeof refreshGrokAccessToken;
528
+ declare const grok_requestGrokDeviceAuthorization: typeof requestGrokDeviceAuthorization;
529
+ declare const grok_resetGrokDiscoveryCache: typeof resetGrokDiscoveryCache;
530
+ declare const grok_resolveGrokTokenEndpoint: typeof resolveGrokTokenEndpoint;
531
+ declare const grok_validateGrokAuthEndpoint: typeof validateGrokAuthEndpoint;
532
+ declare namespace grok {
533
+ export { grok_GROK_OAUTH_CONFIG as GROK_OAUTH_CONFIG, type grok_GrokDeviceAuthorization as GrokDeviceAuthorization, type grok_GrokDevicePoll as GrokDevicePoll, grok_awaitGrokDeviceToken as awaitGrokDeviceToken, grok_grokAccountIdFromAccessToken as grokAccountIdFromAccessToken, grok_isGrokAuthHostname as isGrokAuthHostname, grok_pollGrokDeviceToken as pollGrokDeviceToken, grok_refreshGrokAccessToken as refreshGrokAccessToken, grok_requestGrokDeviceAuthorization as requestGrokDeviceAuthorization, grok_resetGrokDiscoveryCache as resetGrokDiscoveryCache, grok_resolveGrokTokenEndpoint as resolveGrokTokenEndpoint, grok_validateGrokAuthEndpoint as validateGrokAuthEndpoint };
534
+ }
535
+
536
+ export { COPILOT_GITHUB_HEADERS, claude as claudeOAuth, codex as codexOAuth, copilot as copilotOAuth, gemini as geminiOAuth, grok as grokOAuth, kimiFingerprintHeaders, kimi as kimiOAuth };
package/dist/oauth.d.ts CHANGED
@@ -2,6 +2,171 @@ import { FetchLike } from '@omnicross/core/auth/GeminiCodeAssistProjectResolver'
2
2
  export { FetchLike } from '@omnicross/core/auth/GeminiCodeAssistProjectResolver';
3
3
  import { TokenExchangeRequest, OAuthParams } from '@omnicross/contracts/account-tokens-types';
4
4
 
5
+ /**
6
+ * GitHub Copilot OAuth flow — RFC 8628 device grant against the official
7
+ * Copilot CLI app at `github.com` (scope `read:user`).
8
+ *
9
+ * The minted `ghu_` token is LONG-LIVED: refresh is a LOCAL no-op (access and
10
+ * refresh are the same token; a far-future expiry keeps every generic
11
+ * near-expiry path idle). After the user approves, the flow (a) reads the
12
+ * GitHub identity (`/user` → login + email), (b) discovers the plan-advertised
13
+ * Copilot API endpoint (`api.github.com/copilot_internal/user` →
14
+ * `endpoints.api`), and (c) best-effort sweeps the model roster's
15
+ * `POST /models/{id}/policy {"state":"enabled"}` — the Claude/Grok models are
16
+ * policy-gated until enabled.
17
+ *
18
+ * Every Copilot API request carries the mirrored Copilot CLI identity
19
+ * (`copilot/1.0.82` UA + Editor-Version, Copilot-Integration-Id, Copilot-
20
+ * Harness-Id, Openai-Intent) plus `X-GitHub-Api-Version: 2026-08-01` — without
21
+ * the version header the endpoint serves default-tier context limits only
22
+ * (e.g. 264k instead of 1M). NEVER send the API version header to
23
+ * `api.github.com` REST endpoints (they validate it against the REST
24
+ * vocabulary). Relay traffic is agent-initiated CLI traffic, so the per-request
25
+ * dynamic `X-Initiator: agent` / `X-Interaction-Type: conversation-agent`
26
+ * pair is STATIC here — the same classification the official CLI sends, and
27
+ * the one GitHub bills at a 0 premium multiplier.
28
+ *
29
+ * GitHub Enterprise domains ride the SAME device flow on the enterprise host
30
+ * (`https://<domain>/login/{device/code,oauth/access_token}`); the stored
31
+ * `enterpriseUrl` additionally routes inference through `copilot-api.<domain>`
32
+ * and the REST probes (identity/quota) through `api.<domain>`.
33
+ *
34
+ * @module @omnicross/subscriptions/oauth/flows/copilot
35
+ */
36
+
37
+ /** The official Copilot CLI OAuth app (public client). */
38
+ declare const COPILOT_OAUTH_CONFIG: {
39
+ readonly clientId: "Ov23ctDVkRmgkPke0Mmm";
40
+ readonly scope: "read:user";
41
+ readonly deviceEndpoint: "https://github.com/login/device/code";
42
+ readonly tokenEndpoint: "https://github.com/login/oauth/access_token";
43
+ };
44
+ /**
45
+ * Normalize an operator-supplied GitHub Enterprise domain: accept a bare host
46
+ * or a full URL and keep ONLY the lowercase hostname. Empty input and the
47
+ * public github.com hosts mean the personal flow (`undefined`); anything that
48
+ * does not parse as a host THROWS (the caller surfaces the message).
49
+ */
50
+ declare function normalizeCopilotEnterpriseDomain(input: string | undefined): string | undefined;
51
+ /**
52
+ * Device-flow endpoints for a login — the GHE host, or the github.com default
53
+ * when `enterpriseUrl` is absent/normalized-away.
54
+ */
55
+ declare function copilotOAuthUrls(enterpriseUrl?: string): {
56
+ deviceEndpoint: string;
57
+ tokenEndpoint: string;
58
+ };
59
+ /** GitHub's device-poll pacing: each wait scales ×1.2 (×1.4 after slow_down). */
60
+ declare const COPILOT_POLL_INITIAL_MULTIPLIER = 1.2;
61
+ declare const COPILOT_POLL_SLOW_DOWN_MULTIPLIER = 1.4;
62
+ /** Headers for `api.github.com` REST/user endpoints (NO Copilot API version). */
63
+ declare const COPILOT_GITHUB_HEADERS: Record<string, string>;
64
+ /**
65
+ * The STATIC Copilot API identity every inference request carries
66
+ * (mirror of the official CLI's request layer).
67
+ */
68
+ declare const COPILOT_API_HEADERS: Record<string, string>;
69
+ /** Far-future expiry (10 years) — ghu_ tokens have no refresh lifecycle. */
70
+ declare const COPILOT_FAR_FUTURE_MS: number;
71
+ /** Device-authorization response (RFC 8628 §3.2). */
72
+ interface CopilotDeviceAuthorization {
73
+ userCode: string;
74
+ deviceCode: string;
75
+ verificationUri: string;
76
+ /** Poll interval in seconds. */
77
+ interval: number;
78
+ /** Lifetime in seconds. */
79
+ expiresIn: number;
80
+ }
81
+ /** One polled token attempt's outcome. */
82
+ type CopilotDevicePoll = {
83
+ state: 'pending';
84
+ } | {
85
+ state: 'slowDown';
86
+ intervalSeconds?: number;
87
+ } | {
88
+ state: 'done';
89
+ accessToken: string;
90
+ } | {
91
+ state: 'failed';
92
+ message: string;
93
+ };
94
+ /** Request a device code the user approves at `verification_uri`. */
95
+ declare function requestCopilotDeviceAuthorization(fetchImpl: FetchLike, enterpriseUrl?: string): Promise<CopilotDeviceAuthorization>;
96
+ /** Poll the token endpoint ONCE (RFC 8628 §3.5 semantics). */
97
+ declare function pollCopilotDeviceToken(deviceCode: string, fetchImpl: FetchLike, enterpriseUrl?: string): Promise<CopilotDevicePoll>;
98
+ /**
99
+ * Drive the device-code login to completion. GitHub's pacing multiplies each
100
+ * wait (×1.2 baseline, ×1.4 once a slow_down arrives) — deliberately more
101
+ * conservative than the RFC's flat +5s. `onPending` fires after each poll.
102
+ */
103
+ declare function awaitCopilotDeviceToken(authorization: CopilotDeviceAuthorization, fetchImpl: FetchLike, options?: {
104
+ deadlineMs?: number;
105
+ sleep?: (ms: number) => Promise<void>;
106
+ onPending?: () => void;
107
+ /** GHE host — routes the token poll off github.com. */
108
+ enterpriseUrl?: string;
109
+ }): Promise<{
110
+ accessToken: string;
111
+ }>;
112
+ /** The GitHub identity for the minted token (login = the account id). */
113
+ declare function fetchCopilotIdentity(accessToken: string, fetchImpl: FetchLike, enterpriseUrl?: string): Promise<{
114
+ accountId?: string;
115
+ email?: string;
116
+ }>;
117
+ /**
118
+ * Resolve the plan-advertised Copilot API endpoint
119
+ * (`copilot_internal/user` → `endpoints.api`); `undefined` falls back to the
120
+ * canonical personal host at dispatch time.
121
+ */
122
+ declare function discoverCopilotApiEndpoint(accessToken: string, fetchImpl: FetchLike, enterpriseUrl?: string): Promise<string | undefined>;
123
+ /**
124
+ * Enable one policy-gated model (`POST /models/{id}/policy`). Best-effort —
125
+ * the response is ignored and failures never fail the login.
126
+ */
127
+ declare function enableCopilotModel(accessToken: string, modelId: string, baseUrl: string, fetchImpl: FetchLike): Promise<boolean>;
128
+ /**
129
+ * Sweep the model roster's policy-enable endpoint (batched ×5). Best-effort:
130
+ * every failure is swallowed (already-enabled models return non-2xx too).
131
+ */
132
+ declare function enableAllCopilotModels(accessToken: string, config: {
133
+ apiEndpoint?: string;
134
+ enterpriseUrl?: string;
135
+ } | undefined, fetchImpl: FetchLike, onProgress?: (modelId: string, ok: boolean) => void): Promise<void>;
136
+ /**
137
+ * "Refresh" a Copilot token — a LOCAL no-op. GitHub OAuth device tokens are
138
+ * long-lived with no exchange endpoint; the stored access token IS the
139
+ * credential. Returns the same pair with a far-future expiry so every generic
140
+ * refresh path stays a no-network success.
141
+ */
142
+ declare function refreshCopilotToken(accessToken: string): {
143
+ accessToken: string;
144
+ refreshToken: string;
145
+ expiresIn: number;
146
+ };
147
+
148
+ declare const copilot_COPILOT_API_HEADERS: typeof COPILOT_API_HEADERS;
149
+ declare const copilot_COPILOT_FAR_FUTURE_MS: typeof COPILOT_FAR_FUTURE_MS;
150
+ declare const copilot_COPILOT_GITHUB_HEADERS: typeof COPILOT_GITHUB_HEADERS;
151
+ declare const copilot_COPILOT_OAUTH_CONFIG: typeof COPILOT_OAUTH_CONFIG;
152
+ declare const copilot_COPILOT_POLL_INITIAL_MULTIPLIER: typeof COPILOT_POLL_INITIAL_MULTIPLIER;
153
+ declare const copilot_COPILOT_POLL_SLOW_DOWN_MULTIPLIER: typeof COPILOT_POLL_SLOW_DOWN_MULTIPLIER;
154
+ type copilot_CopilotDeviceAuthorization = CopilotDeviceAuthorization;
155
+ type copilot_CopilotDevicePoll = CopilotDevicePoll;
156
+ declare const copilot_awaitCopilotDeviceToken: typeof awaitCopilotDeviceToken;
157
+ declare const copilot_copilotOAuthUrls: typeof copilotOAuthUrls;
158
+ declare const copilot_discoverCopilotApiEndpoint: typeof discoverCopilotApiEndpoint;
159
+ declare const copilot_enableAllCopilotModels: typeof enableAllCopilotModels;
160
+ declare const copilot_enableCopilotModel: typeof enableCopilotModel;
161
+ declare const copilot_fetchCopilotIdentity: typeof fetchCopilotIdentity;
162
+ declare const copilot_normalizeCopilotEnterpriseDomain: typeof normalizeCopilotEnterpriseDomain;
163
+ declare const copilot_pollCopilotDeviceToken: typeof pollCopilotDeviceToken;
164
+ declare const copilot_refreshCopilotToken: typeof refreshCopilotToken;
165
+ declare const copilot_requestCopilotDeviceAuthorization: typeof requestCopilotDeviceAuthorization;
166
+ declare namespace copilot {
167
+ export { copilot_COPILOT_API_HEADERS as COPILOT_API_HEADERS, copilot_COPILOT_FAR_FUTURE_MS as COPILOT_FAR_FUTURE_MS, copilot_COPILOT_GITHUB_HEADERS as COPILOT_GITHUB_HEADERS, copilot_COPILOT_OAUTH_CONFIG as COPILOT_OAUTH_CONFIG, copilot_COPILOT_POLL_INITIAL_MULTIPLIER as COPILOT_POLL_INITIAL_MULTIPLIER, copilot_COPILOT_POLL_SLOW_DOWN_MULTIPLIER as COPILOT_POLL_SLOW_DOWN_MULTIPLIER, type copilot_CopilotDeviceAuthorization as CopilotDeviceAuthorization, type copilot_CopilotDevicePoll as CopilotDevicePoll, copilot_awaitCopilotDeviceToken as awaitCopilotDeviceToken, copilot_copilotOAuthUrls as copilotOAuthUrls, copilot_discoverCopilotApiEndpoint as discoverCopilotApiEndpoint, copilot_enableAllCopilotModels as enableAllCopilotModels, copilot_enableCopilotModel as enableCopilotModel, copilot_fetchCopilotIdentity as fetchCopilotIdentity, copilot_normalizeCopilotEnterpriseDomain as normalizeCopilotEnterpriseDomain, copilot_pollCopilotDeviceToken as pollCopilotDeviceToken, copilot_refreshCopilotToken as refreshCopilotToken, copilot_requestCopilotDeviceAuthorization as requestCopilotDeviceAuthorization };
168
+ }
169
+
5
170
  /**
6
171
  * Kimi Code OAuth flow — RFC 8628 device authorization grant (host-clean).
7
172
  *
@@ -240,4 +405,132 @@ declare namespace gemini {
240
405
  export { gemini_exchangeCodeForTokens as exchangeCodeForTokens, gemini_generateAuthParams as generateAuthParams, gemini_refreshAccessToken as refreshAccessToken };
241
406
  }
242
407
 
243
- export { claude as claudeOAuth, codex as codexOAuth, gemini as geminiOAuth, kimiFingerprintHeaders, kimi as kimiOAuth };
408
+ /**
409
+ * Grok (xAI SuperGrok) OAuth flow — RFC 8628 device authorization grant.
410
+ *
411
+ * Mirrors the public Grok CLI client at `auth.x.ai` (client id reverse-derived
412
+ * from the official CLI, same as the audit source): a form-encoded
413
+ * device-authorization request with the CLI scopes, the user approving at
414
+ * `verification_uri`, and a polled token request whose RFC error codes arrive
415
+ * as HTTP 400 JSON bodies (hence the raw-form POST helper — the shared
416
+ * `postForm` rejects on any `error` body). Refresh is a standard
417
+ * `refresh_token` grant on the same endpoint.
418
+ *
419
+ * The token endpoint is NOT hard-coded: it is resolved through xAI's OIDC
420
+ * discovery document and pinned to HTTPS `x.ai` / `*.x.ai` (the discovery
421
+ * response is long-lived and its endpoint receives every future refresh
422
+ * token, so a drifted document must not redirect credentials off-origin).
423
+ * The discovery result is cached process-wide for an hour.
424
+ *
425
+ * The access token is a JWT; its `sub` claim is the account id. Inference
426
+ * rides `api.x.ai/v1/responses` (the codex-style Responses wire); the weekly
427
+ * credits / unified monthly quota lives behind `cli-chat-proxy.grok.com/v1/
428
+ * billing` (see `GrokAllowanceCollector`).
429
+ *
430
+ * Escape hatches: `GROK_OAUTH_DEVICE_ENDPOINT` / `GROK_OAUTH_DISCOVERY_URL`
431
+ * override the two endpoints (host-pinning still applies to the discovery
432
+ * result).
433
+ *
434
+ * @module @omnicross/subscriptions/oauth/flows/grok
435
+ */
436
+
437
+ /** Grok CLI OAuth configuration (public client, mirrors the official CLI). */
438
+ declare const GROK_OAUTH_CONFIG: {
439
+ readonly clientId: "b1a00492-073a-47ea-816f-4c329264a828";
440
+ readonly deviceAuthorizationEndpoint: string;
441
+ readonly discoveryUrl: string;
442
+ /** The CLI's full scope set — the token must carry `grok-cli:access` for inference. */
443
+ readonly scopes: readonly ["openid", "profile", "email", "offline_access", "grok-cli:access", "api:access"];
444
+ };
445
+ /** Device-authorization response (RFC 8628 §3.2). */
446
+ interface GrokDeviceAuthorization {
447
+ userCode: string;
448
+ deviceCode: string;
449
+ /** Preferred: pre-fills the code when opened in the user's browser. */
450
+ verificationUri: string;
451
+ verificationUriComplete?: string;
452
+ /** Poll interval in seconds (RFC default 5). */
453
+ interval?: number;
454
+ /** Lifetime in seconds. */
455
+ expiresIn?: number;
456
+ }
457
+ /** One polled token attempt's outcome. */
458
+ type GrokDevicePoll = {
459
+ state: 'pending';
460
+ intervalSeconds?: number;
461
+ } | {
462
+ state: 'done';
463
+ accessToken: string;
464
+ refreshToken: string;
465
+ expiresIn: number;
466
+ } | {
467
+ state: 'failed';
468
+ message: string;
469
+ };
470
+ /** `x.ai` or any `*.x.ai` host — the only origins a token endpoint may live on. */
471
+ declare function isGrokAuthHostname(host: string): boolean;
472
+ /**
473
+ * Validate an endpoint URL against the token-endpoint contract: HTTPS and an
474
+ * `x.ai` / `*.x.ai` host. Throws a descriptive error otherwise.
475
+ */
476
+ declare function validateGrokAuthEndpoint(url: string, field: string): string;
477
+ /** Test seam: drop the discovery cache (the cache is process-level). */
478
+ declare function resetGrokDiscoveryCache(): void;
479
+ /**
480
+ * Resolve the OIDC token endpoint via discovery (cached 1h). The document's
481
+ * `token_endpoint` is host-pinned so a compromised or drifted discovery
482
+ * response can never redirect credentials off the xAI origin.
483
+ */
484
+ declare function resolveGrokTokenEndpoint(fetchImpl: FetchLike, timeoutMs?: number): Promise<string>;
485
+ /** Request a device code the user approves at `verification_uri`. */
486
+ declare function requestGrokDeviceAuthorization(fetchImpl: FetchLike): Promise<GrokDeviceAuthorization>;
487
+ /**
488
+ * Poll the token endpoint ONCE. RFC 8628 §3.5 semantics: `authorization_pending`
489
+ * keeps polling, `slow_down` adds 5s to the interval, anything else fails.
490
+ */
491
+ declare function pollGrokDeviceToken(deviceCode: string, tokenEndpoint: string, fetchImpl: FetchLike): Promise<GrokDevicePoll>;
492
+ /**
493
+ * Drive the device-code login to completion: poll at the device flow's
494
+ * interval (`slow_down` +5s each time, applied BEFORE the next wait) until
495
+ * done/expired/denied or `deadlineMs` elapses. `onPending` fires after each
496
+ * pending poll (so a CLI can render a spinner).
497
+ */
498
+ declare function awaitGrokDeviceToken(authorization: GrokDeviceAuthorization, tokenEndpoint: string, fetchImpl: FetchLike, options?: {
499
+ intervalMs?: number;
500
+ deadlineMs?: number;
501
+ sleep?: (ms: number) => Promise<void>;
502
+ onPending?: () => void;
503
+ }): Promise<{
504
+ accessToken: string;
505
+ refreshToken: string;
506
+ expiresIn: number;
507
+ }>;
508
+ /** Refresh the access token with a `refresh_token` grant. */
509
+ declare function refreshGrokAccessToken(refreshToken: string, tokenEndpoint: string, fetchImpl: FetchLike): Promise<{
510
+ accessToken: string;
511
+ refreshToken: string;
512
+ expiresIn: number;
513
+ }>;
514
+ /**
515
+ * Decode the access-token JWT's `sub` claim (no verification — the issuer is
516
+ * trusted; we only read an id).
517
+ */
518
+ declare function grokAccountIdFromAccessToken(accessToken: string): string | undefined;
519
+
520
+ declare const grok_GROK_OAUTH_CONFIG: typeof GROK_OAUTH_CONFIG;
521
+ type grok_GrokDeviceAuthorization = GrokDeviceAuthorization;
522
+ type grok_GrokDevicePoll = GrokDevicePoll;
523
+ declare const grok_awaitGrokDeviceToken: typeof awaitGrokDeviceToken;
524
+ declare const grok_grokAccountIdFromAccessToken: typeof grokAccountIdFromAccessToken;
525
+ declare const grok_isGrokAuthHostname: typeof isGrokAuthHostname;
526
+ declare const grok_pollGrokDeviceToken: typeof pollGrokDeviceToken;
527
+ declare const grok_refreshGrokAccessToken: typeof refreshGrokAccessToken;
528
+ declare const grok_requestGrokDeviceAuthorization: typeof requestGrokDeviceAuthorization;
529
+ declare const grok_resetGrokDiscoveryCache: typeof resetGrokDiscoveryCache;
530
+ declare const grok_resolveGrokTokenEndpoint: typeof resolveGrokTokenEndpoint;
531
+ declare const grok_validateGrokAuthEndpoint: typeof validateGrokAuthEndpoint;
532
+ declare namespace grok {
533
+ export { grok_GROK_OAUTH_CONFIG as GROK_OAUTH_CONFIG, type grok_GrokDeviceAuthorization as GrokDeviceAuthorization, type grok_GrokDevicePoll as GrokDevicePoll, grok_awaitGrokDeviceToken as awaitGrokDeviceToken, grok_grokAccountIdFromAccessToken as grokAccountIdFromAccessToken, grok_isGrokAuthHostname as isGrokAuthHostname, grok_pollGrokDeviceToken as pollGrokDeviceToken, grok_refreshGrokAccessToken as refreshGrokAccessToken, grok_requestGrokDeviceAuthorization as requestGrokDeviceAuthorization, grok_resetGrokDiscoveryCache as resetGrokDiscoveryCache, grok_resolveGrokTokenEndpoint as resolveGrokTokenEndpoint, grok_validateGrokAuthEndpoint as validateGrokAuthEndpoint };
534
+ }
535
+
536
+ export { COPILOT_GITHUB_HEADERS, claude as claudeOAuth, codex as codexOAuth, copilot as copilotOAuth, gemini as geminiOAuth, grok as grokOAuth, kimiFingerprintHeaders, kimi as kimiOAuth };
package/dist/oauth.js CHANGED
@@ -1,15 +1,21 @@
1
1
  import {
2
+ COPILOT_GITHUB_HEADERS,
2
3
  claude_exports,
3
4
  codex_exports,
5
+ copilot_exports,
4
6
  gemini_exports,
7
+ grok_exports,
5
8
  kimiFingerprintHeaders,
6
9
  kimi_exports
7
- } from "./chunk-ZB3GA2Y2.js";
10
+ } from "./chunk-L77HJY6V.js";
8
11
  import "./chunk-MLKGABMK.js";
9
12
  export {
13
+ COPILOT_GITHUB_HEADERS,
10
14
  claude_exports as claudeOAuth,
11
15
  codex_exports as codexOAuth,
16
+ copilot_exports as copilotOAuth,
12
17
  gemini_exports as geminiOAuth,
18
+ grok_exports as grokOAuth,
13
19
  kimiFingerprintHeaders,
14
20
  kimi_exports as kimiOAuth
15
21
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnicross/subscriptions",
3
- "version": "0.3.1",
3
+ "version": "0.4.1",
4
4
  "description": "Omnicross subscription-as-provider auth strategies, OAuth flows, and the OpenCodeGo scenario dispatcher.",
5
5
  "license": "MIT",
6
6
  "author": "Sayo (https://github.com/Dumoedss)",
@@ -52,8 +52,8 @@
52
52
  "typecheck": "tsc -p tsconfig.typecheck.json --noEmit"
53
53
  },
54
54
  "dependencies": {
55
- "@omnicross/contracts": "^0.3.1",
56
- "@omnicross/core": "^0.3.1",
55
+ "@omnicross/contracts": "^0.4.1",
56
+ "@omnicross/core": "^0.4.1",
57
57
  "js-tiktoken": "^1.0.21",
58
58
  "sharp": "^0.35.4"
59
59
  }