@omnicross/subscriptions 0.3.0 → 0.3.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/{chunk-UXUMAXZJ.cjs → chunk-B2LHAP4F.cjs} +191 -15
- package/dist/{chunk-4HYDPKHR.js → chunk-ZB3GA2Y2.js} +194 -18
- package/dist/index.cjs +127 -63
- package/dist/index.d.cts +25 -2
- package/dist/index.d.ts +25 -2
- package/dist/index.js +77 -13
- package/dist/oauth.cjs +6 -2
- package/dist/oauth.d.cts +118 -1
- package/dist/oauth.d.ts +118 -1
- package/dist/oauth.js +7 -3
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
claude_exports,
|
|
3
3
|
codex_exports,
|
|
4
|
-
gemini_exports
|
|
5
|
-
|
|
4
|
+
gemini_exports,
|
|
5
|
+
kimiFingerprintHeaders,
|
|
6
|
+
kimi_exports
|
|
7
|
+
} from "./chunk-ZB3GA2Y2.js";
|
|
6
8
|
import {
|
|
7
9
|
accountSupportsModel,
|
|
8
10
|
remapReportForAccount
|
|
@@ -125,13 +127,15 @@ var ACCOUNTS_KEY = {
|
|
|
125
127
|
claude: "claudeAccounts",
|
|
126
128
|
codex: "codexAccounts",
|
|
127
129
|
gemini: "geminiAccounts",
|
|
128
|
-
opencodego: "opencodegoAccounts"
|
|
130
|
+
opencodego: "opencodegoAccounts",
|
|
131
|
+
kimi: "kimiAccounts"
|
|
129
132
|
};
|
|
130
133
|
var ACTIVE_KEY = {
|
|
131
134
|
claude: "activeClaudeAccountId",
|
|
132
135
|
codex: "activeCodexAccountId",
|
|
133
136
|
gemini: "activeGeminiAccountId",
|
|
134
|
-
opencodego: "activeOpencodegoAccountId"
|
|
137
|
+
opencodego: "activeOpencodegoAccountId",
|
|
138
|
+
kimi: "activeKimiAccountId"
|
|
135
139
|
};
|
|
136
140
|
function gateSchedulable(accounts, providerId, health, now, resolvedModel, supportedModelsById) {
|
|
137
141
|
if (!health && !resolvedModel || accounts.length < 2) return accounts;
|
|
@@ -421,13 +425,17 @@ var OAuthBearerAuthStrategy = class {
|
|
|
421
425
|
return;
|
|
422
426
|
}
|
|
423
427
|
headers["Authorization"] = `Bearer ${token}`;
|
|
428
|
+
if (this.providerId === "kimi") {
|
|
429
|
+
const deviceId = await this.resolveKimiDeviceId(hints?.sessionKey);
|
|
430
|
+
Object.assign(headers, kimiFingerprintHeaders(deviceId));
|
|
431
|
+
}
|
|
424
432
|
}
|
|
425
433
|
async onUnauthorized(sessionKey) {
|
|
426
434
|
const byId = await refreshSelectedAccount(this.selector, this.tokens, this.mutex, this.providerId, sessionKey);
|
|
427
435
|
if (byId !== null) return byId;
|
|
428
436
|
return this.mutex.run(`${this.providerId}:refresh`, async () => {
|
|
429
437
|
try {
|
|
430
|
-
return this.
|
|
438
|
+
return this.refreshActive();
|
|
431
439
|
} catch (err) {
|
|
432
440
|
console.warn(`[OAuthBearerAuthStrategy] ${this.providerId} refresh failed:`, err);
|
|
433
441
|
return false;
|
|
@@ -436,7 +444,7 @@ var OAuthBearerAuthStrategy = class {
|
|
|
436
444
|
}
|
|
437
445
|
async describeStatus() {
|
|
438
446
|
const config = await this.tokens.getFullConfig();
|
|
439
|
-
const entry = this.
|
|
447
|
+
const entry = this.tokenBlock(config);
|
|
440
448
|
if (!entry?.accessToken) {
|
|
441
449
|
return { providerId: this.providerId, ok: false, reason: "missing-credential" };
|
|
442
450
|
}
|
|
@@ -453,22 +461,53 @@ var OAuthBearerAuthStrategy = class {
|
|
|
453
461
|
/** Read the current token, refreshing in-line if it's within the lead window. */
|
|
454
462
|
async resolveAccessToken() {
|
|
455
463
|
const config = await this.tokens.getFullConfig();
|
|
456
|
-
const entry = this.
|
|
464
|
+
const entry = this.tokenBlock(config);
|
|
457
465
|
if (!entry?.accessToken) return null;
|
|
458
466
|
const expiresAtMs = entry.expiresAt ? new Date(entry.expiresAt).getTime() : 0;
|
|
459
467
|
const expiringSoon = expiresAtMs > 0 && Date.now() >= expiresAtMs - REFRESH_LEAD_MS;
|
|
460
468
|
if (expiringSoon && entry.refreshToken) {
|
|
461
469
|
const refreshed = await this.mutex.run(`${this.providerId}:refresh`, async () => {
|
|
462
|
-
return this.
|
|
470
|
+
return this.refreshActive();
|
|
463
471
|
});
|
|
464
472
|
if (!refreshed) return null;
|
|
465
473
|
const fresh = await this.tokens.getFullConfig();
|
|
466
|
-
|
|
467
|
-
return freshEntry?.accessToken ?? null;
|
|
474
|
+
return this.tokenBlock(fresh)?.accessToken ?? null;
|
|
468
475
|
}
|
|
469
476
|
if (entry.status === "expired") return null;
|
|
470
477
|
return entry.accessToken;
|
|
471
478
|
}
|
|
479
|
+
/** The active account's refresh, dispatched per provider. */
|
|
480
|
+
refreshActive() {
|
|
481
|
+
switch (this.providerId) {
|
|
482
|
+
case "codex":
|
|
483
|
+
return this.tokens.refreshCodexToken();
|
|
484
|
+
case "gemini":
|
|
485
|
+
return this.tokens.refreshGeminiToken();
|
|
486
|
+
case "kimi":
|
|
487
|
+
return this.tokens.refreshKimiToken ? this.tokens.refreshKimiToken() : Promise.resolve(false);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
tokenBlock(config) {
|
|
491
|
+
switch (this.providerId) {
|
|
492
|
+
case "codex":
|
|
493
|
+
return config.codex;
|
|
494
|
+
case "gemini":
|
|
495
|
+
return config.gemini;
|
|
496
|
+
case "kimi":
|
|
497
|
+
return config.kimi;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Best-effort device id for the fingerprint header. Selection already ran in
|
|
502
|
+
* `applyHeaders`; rather than re-deriving it, read the active block's id (a
|
|
503
|
+
* pool-served non-active account momentarily reports the active id — the
|
|
504
|
+
* header is per-INSTALL identity, so this is cosmetic, not auth).
|
|
505
|
+
*/
|
|
506
|
+
async resolveKimiDeviceId(_sessionKey) {
|
|
507
|
+
void _sessionKey;
|
|
508
|
+
const config = await this.tokens.getFullConfig();
|
|
509
|
+
return config.kimi?.deviceId ?? config.kimiAccounts?.[0]?.tokens.deviceId;
|
|
510
|
+
}
|
|
472
511
|
};
|
|
473
512
|
|
|
474
513
|
// src/auth/PassThroughAuthStrategy.ts
|
|
@@ -615,7 +654,8 @@ var DISPLAY_NAMES = {
|
|
|
615
654
|
claude: "Claude (Anthropic OAuth)",
|
|
616
655
|
codex: "Codex (ChatGPT OAuth)",
|
|
617
656
|
gemini: "Gemini (Google OAuth)",
|
|
618
|
-
opencodego: "OpenCodeGo (Bearer key)"
|
|
657
|
+
opencodego: "OpenCodeGo (Bearer key)",
|
|
658
|
+
kimi: "Kimi Code (Moonshot OAuth)"
|
|
619
659
|
};
|
|
620
660
|
var SubscriptionAccountService = class {
|
|
621
661
|
mutex = new RefreshMutex();
|
|
@@ -629,7 +669,8 @@ var SubscriptionAccountService = class {
|
|
|
629
669
|
["claude", new PassThroughAuthStrategy(tokens, this.mutex, this.selector, health)],
|
|
630
670
|
["codex", new OAuthBearerAuthStrategy("codex", tokens, this.mutex, this.selector, health)],
|
|
631
671
|
["gemini", new OAuthBearerAuthStrategy("gemini", tokens, this.mutex, this.selector, health)],
|
|
632
|
-
["opencodego", new StaticBearerAuthStrategy(tokens, this.selector, health)]
|
|
672
|
+
["opencodego", new StaticBearerAuthStrategy(tokens, this.selector, health)],
|
|
673
|
+
["kimi", new OAuthBearerAuthStrategy("kimi", tokens, this.mutex, this.selector, health)]
|
|
633
674
|
]);
|
|
634
675
|
}
|
|
635
676
|
/** Returns the strategy bound to a subscription provider, or `null` for unknown ids. */
|
|
@@ -1079,7 +1120,8 @@ var SubscriptionProviderRegistry = class {
|
|
|
1079
1120
|
const codex = this.accounts.getStrategy("codex");
|
|
1080
1121
|
const gemini = this.accounts.getStrategy("gemini");
|
|
1081
1122
|
const opencodego = this.accounts.getStrategy("opencodego");
|
|
1082
|
-
|
|
1123
|
+
const kimi = this.accounts.getStrategy("kimi");
|
|
1124
|
+
if (!claude || !codex || !gemini || !opencodego || !kimi) {
|
|
1083
1125
|
throw new Error("[SubscriptionProviderRegistry] Missing strategy in SubscriptionAccountService");
|
|
1084
1126
|
}
|
|
1085
1127
|
this.profiles = /* @__PURE__ */ new Map([
|
|
@@ -1221,6 +1263,25 @@ var SubscriptionProviderRegistry = class {
|
|
|
1221
1263
|
// profile sets it — claude / codex / gemini leave it UNSET (no-op).
|
|
1222
1264
|
recordModelOutcome: (modelId, ok) => ok ? this.breaker.recordSuccess(modelId) : this.breaker.recordFailure(modelId)
|
|
1223
1265
|
}
|
|
1266
|
+
],
|
|
1267
|
+
[
|
|
1268
|
+
"kimi",
|
|
1269
|
+
{
|
|
1270
|
+
providerId: "kimi",
|
|
1271
|
+
displayName: "Kimi Code (Moonshot OAuth)",
|
|
1272
|
+
authStrategy: kimi,
|
|
1273
|
+
mode: "transformer",
|
|
1274
|
+
// Kimi Code's subscription surface speaks standard Anthropic Messages
|
|
1275
|
+
// at `api.kimi.com/coding/v1/messages` (plus an OpenAI chat face on
|
|
1276
|
+
// /chat/completions that a BYO kimi row already covers). The messages
|
|
1277
|
+
// URL ending in `/v1/messages` makes the core plan builder treat it as
|
|
1278
|
+
// same-format — the SDK's Anthropic body relays verbatim, with the
|
|
1279
|
+
// OAuth bearer + X-Msh-* fingerprint headers injected by the strategy.
|
|
1280
|
+
resolveUpstreamUrl: () => "https://api.kimi.com/coding/v1/messages",
|
|
1281
|
+
// Route-to only (Responses/Chat ingress): Unified → Anthropic Messages.
|
|
1282
|
+
providerTransformerNames: ["anthropic"],
|
|
1283
|
+
modelTransformerNames: []
|
|
1284
|
+
}
|
|
1224
1285
|
]
|
|
1225
1286
|
]);
|
|
1226
1287
|
}
|
|
@@ -2548,6 +2609,9 @@ export {
|
|
|
2548
2609
|
gemini_exports as geminiOAuth,
|
|
2549
2610
|
getSubscriptionAccountService,
|
|
2550
2611
|
getSubscriptionProviderRegistry,
|
|
2612
|
+
kimiFingerprintHeaders,
|
|
2613
|
+
kimi_exports as kimiOAuth,
|
|
2614
|
+
normalizeOpenCodeGoBaseUrl,
|
|
2551
2615
|
setSubscriptionAccountService,
|
|
2552
2616
|
setSubscriptionProviderRegistry
|
|
2553
2617
|
};
|
package/dist/oauth.cjs
CHANGED
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
var _chunkB2LHAP4Fcjs = require('./chunk-B2LHAP4F.cjs');
|
|
6
8
|
require('./chunk-75ZPJI57.cjs');
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
exports.claudeOAuth = _chunkB2LHAP4Fcjs.claude_exports; exports.codexOAuth = _chunkB2LHAP4Fcjs.codex_exports; exports.geminiOAuth = _chunkB2LHAP4Fcjs.gemini_exports; exports.kimiFingerprintHeaders = _chunkB2LHAP4Fcjs.kimiFingerprintHeaders; exports.kimiOAuth = _chunkB2LHAP4Fcjs.kimi_exports;
|
package/dist/oauth.d.cts
CHANGED
|
@@ -2,6 +2,123 @@ 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
|
+
* Kimi Code OAuth flow — RFC 8628 device authorization grant (host-clean).
|
|
7
|
+
*
|
|
8
|
+
* Matches the official Kimi CLI's client at `auth.kimi.com`: a form-encoded
|
|
9
|
+
* device-authorization request, the user approving at `verification_uri`, and a
|
|
10
|
+
* polled token request whose RFC error codes (`authorization_pending` /
|
|
11
|
+
* `slow_down` / `expired_token` / `access_denied`) arrive as HTTP 400 bodies —
|
|
12
|
+
* which is why the poll does NOT use `postForm` (that helper rejects on
|
|
13
|
+
* `error`). No PKCE, no client secret, no scopes. Refresh is a standard
|
|
14
|
+
* `refresh_token` grant on the same endpoint.
|
|
15
|
+
*
|
|
16
|
+
* The access token's JWT payload carries `user_id` (fallback `sub`) — that is
|
|
17
|
+
* the account id. Inference and the `/coding/v1/usages` quota endpoint ride
|
|
18
|
+
* `api.kimi.com/coding/v1`; every Kimi request also carries the
|
|
19
|
+
* `X-Msh-*` fingerprint headers (`kimiFingerprintHeaders`).
|
|
20
|
+
*
|
|
21
|
+
* @module @omnicross/subscriptions/oauth/flows/kimi
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** Kimi CLI OAuth configuration (public client, matches the official CLI). */
|
|
25
|
+
declare const KIMI_OAUTH_CONFIG: {
|
|
26
|
+
readonly clientId: "17e5f671-d194-4dfb-9706-5516cb48c098";
|
|
27
|
+
readonly deviceAuthorizationEndpoint: "https://auth.kimi.com/api/oauth/device_authorization";
|
|
28
|
+
readonly tokenEndpoint: "https://auth.kimi.com/api/oauth/token";
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* The client version reported in `User-Agent`/`X-Msh-Version`. Kimi's backend
|
|
32
|
+
* gates on the CLI identity; this mirrors a current official CLI version.
|
|
33
|
+
* Overridable via `KIMI_CLI_VERSION` when the upstream moves.
|
|
34
|
+
*/
|
|
35
|
+
declare const KIMI_CLI_VERSION: string;
|
|
36
|
+
/** Device-authorization response (RFC 8628 §3.2). */
|
|
37
|
+
interface KimiDeviceAuthorization {
|
|
38
|
+
userCode: string;
|
|
39
|
+
deviceCode: string;
|
|
40
|
+
/** Preferred: pre-fills the code when opened in the user's browser. */
|
|
41
|
+
verificationUri: string;
|
|
42
|
+
verificationUriComplete?: string;
|
|
43
|
+
/** Poll interval in seconds (RFC default 5). */
|
|
44
|
+
interval?: number;
|
|
45
|
+
/** Lifetime in seconds. */
|
|
46
|
+
expiresIn?: number;
|
|
47
|
+
}
|
|
48
|
+
/** One polled token attempt's outcome. */
|
|
49
|
+
type KimiDevicePoll = {
|
|
50
|
+
state: 'pending';
|
|
51
|
+
intervalSeconds?: number;
|
|
52
|
+
} | {
|
|
53
|
+
state: 'done';
|
|
54
|
+
accessToken: string;
|
|
55
|
+
refreshToken: string;
|
|
56
|
+
expiresIn: number;
|
|
57
|
+
} | {
|
|
58
|
+
state: 'failed';
|
|
59
|
+
message: string;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* The fingerprint headers every Kimi API request carries (auth, inference,
|
|
63
|
+
* usage). `deviceId` is the per-account stable device id stored on the token
|
|
64
|
+
* config — NOT a host-global value, so two accounts on one install present two
|
|
65
|
+
* device identities, matching how the CLI scopes its `kimi-device-id` file per
|
|
66
|
+
* credential store.
|
|
67
|
+
*/
|
|
68
|
+
declare function kimiFingerprintHeaders(deviceId: string | undefined): Record<string, string>;
|
|
69
|
+
/** Mint the stable per-account device id (hex UUID, no dashes). */
|
|
70
|
+
declare function generateKimiDeviceId(): string;
|
|
71
|
+
/** Request a device code the user approves at `verification_uri`. */
|
|
72
|
+
declare function requestDeviceAuthorization(fetchImpl: FetchLike, fingerprint?: Record<string, string>): Promise<KimiDeviceAuthorization>;
|
|
73
|
+
/**
|
|
74
|
+
* Poll the token endpoint ONCE. RFC 8628 §3.5 semantics: `authorization_pending`
|
|
75
|
+
* keeps polling, `slow_down` adds 5s to the interval, anything else fails. The
|
|
76
|
+
* error codes arrive on HTTP 400 with an `error` JSON field, so this deliberately
|
|
77
|
+
* does NOT route through `postForm` (which rejects on any `error` body).
|
|
78
|
+
*/
|
|
79
|
+
declare function pollDeviceToken(deviceCode: string, fetchImpl: FetchLike, fingerprint?: Record<string, string>): Promise<KimiDevicePoll>;
|
|
80
|
+
/**
|
|
81
|
+
* Drive the device-code login to completion: poll at the device flow's interval
|
|
82
|
+
* (`slow_down` +5s each time) until done/expired/denied or `deadlineMs` elapses.
|
|
83
|
+
* `onPending` fires after each pending poll (so a CLI can render a spinner).
|
|
84
|
+
*/
|
|
85
|
+
declare function awaitDeviceToken(authorization: KimiDeviceAuthorization, fetchImpl: FetchLike, options?: {
|
|
86
|
+
fingerprint?: Record<string, string>;
|
|
87
|
+
intervalMs?: number;
|
|
88
|
+
deadlineMs?: number;
|
|
89
|
+
sleep?: (ms: number) => Promise<void>;
|
|
90
|
+
onPending?: () => void;
|
|
91
|
+
}): Promise<{
|
|
92
|
+
accessToken: string;
|
|
93
|
+
refreshToken: string;
|
|
94
|
+
expiresIn: number;
|
|
95
|
+
}>;
|
|
96
|
+
/** Refresh the access token with a `refresh_token` grant. */
|
|
97
|
+
declare function refreshAccessToken$3(refreshToken: string, fetchImpl: FetchLike, fingerprint?: Record<string, string>): Promise<{
|
|
98
|
+
accessToken: string;
|
|
99
|
+
refreshToken: string;
|
|
100
|
+
expiresIn: number;
|
|
101
|
+
}>;
|
|
102
|
+
/**
|
|
103
|
+
* Decode the access-token JWT's `user_id | sub` claim (no verification — the
|
|
104
|
+
* issuer is trusted; we only read an id).
|
|
105
|
+
*/
|
|
106
|
+
declare function kimiAccountIdFromAccessToken(accessToken: string): string | undefined;
|
|
107
|
+
|
|
108
|
+
declare const kimi_KIMI_CLI_VERSION: typeof KIMI_CLI_VERSION;
|
|
109
|
+
declare const kimi_KIMI_OAUTH_CONFIG: typeof KIMI_OAUTH_CONFIG;
|
|
110
|
+
type kimi_KimiDeviceAuthorization = KimiDeviceAuthorization;
|
|
111
|
+
type kimi_KimiDevicePoll = KimiDevicePoll;
|
|
112
|
+
declare const kimi_awaitDeviceToken: typeof awaitDeviceToken;
|
|
113
|
+
declare const kimi_generateKimiDeviceId: typeof generateKimiDeviceId;
|
|
114
|
+
declare const kimi_kimiAccountIdFromAccessToken: typeof kimiAccountIdFromAccessToken;
|
|
115
|
+
declare const kimi_kimiFingerprintHeaders: typeof kimiFingerprintHeaders;
|
|
116
|
+
declare const kimi_pollDeviceToken: typeof pollDeviceToken;
|
|
117
|
+
declare const kimi_requestDeviceAuthorization: typeof requestDeviceAuthorization;
|
|
118
|
+
declare namespace kimi {
|
|
119
|
+
export { kimi_KIMI_CLI_VERSION as KIMI_CLI_VERSION, kimi_KIMI_OAUTH_CONFIG as KIMI_OAUTH_CONFIG, type kimi_KimiDeviceAuthorization as KimiDeviceAuthorization, type kimi_KimiDevicePoll as KimiDevicePoll, kimi_awaitDeviceToken as awaitDeviceToken, kimi_generateKimiDeviceId as generateKimiDeviceId, kimi_kimiAccountIdFromAccessToken as kimiAccountIdFromAccessToken, kimi_kimiFingerprintHeaders as kimiFingerprintHeaders, kimi_pollDeviceToken as pollDeviceToken, refreshAccessToken$3 as refreshAccessToken, kimi_requestDeviceAuthorization as requestDeviceAuthorization };
|
|
120
|
+
}
|
|
121
|
+
|
|
5
122
|
/**
|
|
6
123
|
* Claude OAuth flow — host-clean PKCE logic.
|
|
7
124
|
*
|
|
@@ -123,4 +240,4 @@ declare namespace gemini {
|
|
|
123
240
|
export { gemini_exchangeCodeForTokens as exchangeCodeForTokens, gemini_generateAuthParams as generateAuthParams, gemini_refreshAccessToken as refreshAccessToken };
|
|
124
241
|
}
|
|
125
242
|
|
|
126
|
-
export { claude as claudeOAuth, codex as codexOAuth, gemini as geminiOAuth };
|
|
243
|
+
export { claude as claudeOAuth, codex as codexOAuth, gemini as geminiOAuth, kimiFingerprintHeaders, kimi as kimiOAuth };
|
package/dist/oauth.d.ts
CHANGED
|
@@ -2,6 +2,123 @@ 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
|
+
* Kimi Code OAuth flow — RFC 8628 device authorization grant (host-clean).
|
|
7
|
+
*
|
|
8
|
+
* Matches the official Kimi CLI's client at `auth.kimi.com`: a form-encoded
|
|
9
|
+
* device-authorization request, the user approving at `verification_uri`, and a
|
|
10
|
+
* polled token request whose RFC error codes (`authorization_pending` /
|
|
11
|
+
* `slow_down` / `expired_token` / `access_denied`) arrive as HTTP 400 bodies —
|
|
12
|
+
* which is why the poll does NOT use `postForm` (that helper rejects on
|
|
13
|
+
* `error`). No PKCE, no client secret, no scopes. Refresh is a standard
|
|
14
|
+
* `refresh_token` grant on the same endpoint.
|
|
15
|
+
*
|
|
16
|
+
* The access token's JWT payload carries `user_id` (fallback `sub`) — that is
|
|
17
|
+
* the account id. Inference and the `/coding/v1/usages` quota endpoint ride
|
|
18
|
+
* `api.kimi.com/coding/v1`; every Kimi request also carries the
|
|
19
|
+
* `X-Msh-*` fingerprint headers (`kimiFingerprintHeaders`).
|
|
20
|
+
*
|
|
21
|
+
* @module @omnicross/subscriptions/oauth/flows/kimi
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** Kimi CLI OAuth configuration (public client, matches the official CLI). */
|
|
25
|
+
declare const KIMI_OAUTH_CONFIG: {
|
|
26
|
+
readonly clientId: "17e5f671-d194-4dfb-9706-5516cb48c098";
|
|
27
|
+
readonly deviceAuthorizationEndpoint: "https://auth.kimi.com/api/oauth/device_authorization";
|
|
28
|
+
readonly tokenEndpoint: "https://auth.kimi.com/api/oauth/token";
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* The client version reported in `User-Agent`/`X-Msh-Version`. Kimi's backend
|
|
32
|
+
* gates on the CLI identity; this mirrors a current official CLI version.
|
|
33
|
+
* Overridable via `KIMI_CLI_VERSION` when the upstream moves.
|
|
34
|
+
*/
|
|
35
|
+
declare const KIMI_CLI_VERSION: string;
|
|
36
|
+
/** Device-authorization response (RFC 8628 §3.2). */
|
|
37
|
+
interface KimiDeviceAuthorization {
|
|
38
|
+
userCode: string;
|
|
39
|
+
deviceCode: string;
|
|
40
|
+
/** Preferred: pre-fills the code when opened in the user's browser. */
|
|
41
|
+
verificationUri: string;
|
|
42
|
+
verificationUriComplete?: string;
|
|
43
|
+
/** Poll interval in seconds (RFC default 5). */
|
|
44
|
+
interval?: number;
|
|
45
|
+
/** Lifetime in seconds. */
|
|
46
|
+
expiresIn?: number;
|
|
47
|
+
}
|
|
48
|
+
/** One polled token attempt's outcome. */
|
|
49
|
+
type KimiDevicePoll = {
|
|
50
|
+
state: 'pending';
|
|
51
|
+
intervalSeconds?: number;
|
|
52
|
+
} | {
|
|
53
|
+
state: 'done';
|
|
54
|
+
accessToken: string;
|
|
55
|
+
refreshToken: string;
|
|
56
|
+
expiresIn: number;
|
|
57
|
+
} | {
|
|
58
|
+
state: 'failed';
|
|
59
|
+
message: string;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* The fingerprint headers every Kimi API request carries (auth, inference,
|
|
63
|
+
* usage). `deviceId` is the per-account stable device id stored on the token
|
|
64
|
+
* config — NOT a host-global value, so two accounts on one install present two
|
|
65
|
+
* device identities, matching how the CLI scopes its `kimi-device-id` file per
|
|
66
|
+
* credential store.
|
|
67
|
+
*/
|
|
68
|
+
declare function kimiFingerprintHeaders(deviceId: string | undefined): Record<string, string>;
|
|
69
|
+
/** Mint the stable per-account device id (hex UUID, no dashes). */
|
|
70
|
+
declare function generateKimiDeviceId(): string;
|
|
71
|
+
/** Request a device code the user approves at `verification_uri`. */
|
|
72
|
+
declare function requestDeviceAuthorization(fetchImpl: FetchLike, fingerprint?: Record<string, string>): Promise<KimiDeviceAuthorization>;
|
|
73
|
+
/**
|
|
74
|
+
* Poll the token endpoint ONCE. RFC 8628 §3.5 semantics: `authorization_pending`
|
|
75
|
+
* keeps polling, `slow_down` adds 5s to the interval, anything else fails. The
|
|
76
|
+
* error codes arrive on HTTP 400 with an `error` JSON field, so this deliberately
|
|
77
|
+
* does NOT route through `postForm` (which rejects on any `error` body).
|
|
78
|
+
*/
|
|
79
|
+
declare function pollDeviceToken(deviceCode: string, fetchImpl: FetchLike, fingerprint?: Record<string, string>): Promise<KimiDevicePoll>;
|
|
80
|
+
/**
|
|
81
|
+
* Drive the device-code login to completion: poll at the device flow's interval
|
|
82
|
+
* (`slow_down` +5s each time) until done/expired/denied or `deadlineMs` elapses.
|
|
83
|
+
* `onPending` fires after each pending poll (so a CLI can render a spinner).
|
|
84
|
+
*/
|
|
85
|
+
declare function awaitDeviceToken(authorization: KimiDeviceAuthorization, fetchImpl: FetchLike, options?: {
|
|
86
|
+
fingerprint?: Record<string, string>;
|
|
87
|
+
intervalMs?: number;
|
|
88
|
+
deadlineMs?: number;
|
|
89
|
+
sleep?: (ms: number) => Promise<void>;
|
|
90
|
+
onPending?: () => void;
|
|
91
|
+
}): Promise<{
|
|
92
|
+
accessToken: string;
|
|
93
|
+
refreshToken: string;
|
|
94
|
+
expiresIn: number;
|
|
95
|
+
}>;
|
|
96
|
+
/** Refresh the access token with a `refresh_token` grant. */
|
|
97
|
+
declare function refreshAccessToken$3(refreshToken: string, fetchImpl: FetchLike, fingerprint?: Record<string, string>): Promise<{
|
|
98
|
+
accessToken: string;
|
|
99
|
+
refreshToken: string;
|
|
100
|
+
expiresIn: number;
|
|
101
|
+
}>;
|
|
102
|
+
/**
|
|
103
|
+
* Decode the access-token JWT's `user_id | sub` claim (no verification — the
|
|
104
|
+
* issuer is trusted; we only read an id).
|
|
105
|
+
*/
|
|
106
|
+
declare function kimiAccountIdFromAccessToken(accessToken: string): string | undefined;
|
|
107
|
+
|
|
108
|
+
declare const kimi_KIMI_CLI_VERSION: typeof KIMI_CLI_VERSION;
|
|
109
|
+
declare const kimi_KIMI_OAUTH_CONFIG: typeof KIMI_OAUTH_CONFIG;
|
|
110
|
+
type kimi_KimiDeviceAuthorization = KimiDeviceAuthorization;
|
|
111
|
+
type kimi_KimiDevicePoll = KimiDevicePoll;
|
|
112
|
+
declare const kimi_awaitDeviceToken: typeof awaitDeviceToken;
|
|
113
|
+
declare const kimi_generateKimiDeviceId: typeof generateKimiDeviceId;
|
|
114
|
+
declare const kimi_kimiAccountIdFromAccessToken: typeof kimiAccountIdFromAccessToken;
|
|
115
|
+
declare const kimi_kimiFingerprintHeaders: typeof kimiFingerprintHeaders;
|
|
116
|
+
declare const kimi_pollDeviceToken: typeof pollDeviceToken;
|
|
117
|
+
declare const kimi_requestDeviceAuthorization: typeof requestDeviceAuthorization;
|
|
118
|
+
declare namespace kimi {
|
|
119
|
+
export { kimi_KIMI_CLI_VERSION as KIMI_CLI_VERSION, kimi_KIMI_OAUTH_CONFIG as KIMI_OAUTH_CONFIG, type kimi_KimiDeviceAuthorization as KimiDeviceAuthorization, type kimi_KimiDevicePoll as KimiDevicePoll, kimi_awaitDeviceToken as awaitDeviceToken, kimi_generateKimiDeviceId as generateKimiDeviceId, kimi_kimiAccountIdFromAccessToken as kimiAccountIdFromAccessToken, kimi_kimiFingerprintHeaders as kimiFingerprintHeaders, kimi_pollDeviceToken as pollDeviceToken, refreshAccessToken$3 as refreshAccessToken, kimi_requestDeviceAuthorization as requestDeviceAuthorization };
|
|
120
|
+
}
|
|
121
|
+
|
|
5
122
|
/**
|
|
6
123
|
* Claude OAuth flow — host-clean PKCE logic.
|
|
7
124
|
*
|
|
@@ -123,4 +240,4 @@ declare namespace gemini {
|
|
|
123
240
|
export { gemini_exchangeCodeForTokens as exchangeCodeForTokens, gemini_generateAuthParams as generateAuthParams, gemini_refreshAccessToken as refreshAccessToken };
|
|
124
241
|
}
|
|
125
242
|
|
|
126
|
-
export { claude as claudeOAuth, codex as codexOAuth, gemini as geminiOAuth };
|
|
243
|
+
export { claude as claudeOAuth, codex as codexOAuth, gemini as geminiOAuth, kimiFingerprintHeaders, kimi as kimiOAuth };
|
package/dist/oauth.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
claude_exports,
|
|
3
3
|
codex_exports,
|
|
4
|
-
gemini_exports
|
|
5
|
-
|
|
4
|
+
gemini_exports,
|
|
5
|
+
kimiFingerprintHeaders,
|
|
6
|
+
kimi_exports
|
|
7
|
+
} from "./chunk-ZB3GA2Y2.js";
|
|
6
8
|
import "./chunk-MLKGABMK.js";
|
|
7
9
|
export {
|
|
8
10
|
claude_exports as claudeOAuth,
|
|
9
11
|
codex_exports as codexOAuth,
|
|
10
|
-
gemini_exports as geminiOAuth
|
|
12
|
+
gemini_exports as geminiOAuth,
|
|
13
|
+
kimiFingerprintHeaders,
|
|
14
|
+
kimi_exports as kimiOAuth
|
|
11
15
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omnicross/subscriptions",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.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.
|
|
56
|
-
"@omnicross/core": "^0.3.
|
|
55
|
+
"@omnicross/contracts": "^0.3.1",
|
|
56
|
+
"@omnicross/core": "^0.3.1",
|
|
57
57
|
"js-tiktoken": "^1.0.21",
|
|
58
58
|
"sharp": "^0.35.4"
|
|
59
59
|
}
|