@oh-my-pi/pi-ai 8.11.14 → 8.12.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-ai",
3
- "version": "8.11.14",
3
+ "version": "8.12.2",
4
4
  "description": "Unified LLM API with automatic model discovery and provider configuration",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -63,7 +63,7 @@
63
63
  "@connectrpc/connect-node": "^2.1.1",
64
64
  "@google/genai": "^1.38.0",
65
65
  "@mistralai/mistralai": "^1.13.0",
66
- "@oh-my-pi/pi-utils": "8.11.14",
66
+ "@oh-my-pi/pi-utils": "8.12.2",
67
67
  "@sinclair/typebox": "^0.34.48",
68
68
  "@smithy/node-http-handler": "^4.4.8",
69
69
  "ajv": "^8.17.1",
@@ -15,6 +15,7 @@ const FIVE_HOURS_MS = 5 * 60 * 60 * 1000;
15
15
  const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
16
16
  const MAX_RETRIES = 3;
17
17
  const BASE_RETRY_DELAY_MS = 500;
18
+ const PROFILE_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
18
19
 
19
20
  const CLAUDE_HEADERS = {
20
21
  accept: "application/json, text/plain, */*",
@@ -160,6 +161,64 @@ async function fetchUsagePayload(
160
161
  return lastPayload ? { payload: lastPayload, orgId: lastOrgId } : null;
161
162
  }
162
163
 
164
+ interface ClaudeProfile {
165
+ account?: {
166
+ uuid?: string;
167
+ email?: string;
168
+ };
169
+ }
170
+
171
+ async function fetchProfile(
172
+ baseUrl: string,
173
+ headers: Record<string, string>,
174
+ ctx: UsageFetchContext,
175
+ signal?: AbortSignal,
176
+ ): Promise<ClaudeProfile | null> {
177
+ const url = `${baseUrl}/profile`;
178
+ try {
179
+ const response = await ctx.fetch(url, { headers, signal });
180
+ if (!response.ok) return null;
181
+ return (await response.json()) as ClaudeProfile;
182
+ } catch {
183
+ return null;
184
+ }
185
+ }
186
+
187
+ function buildProfileCacheKey(params: UsageFetchParams): string {
188
+ const credential = params.credential;
189
+ const token = credential.accessToken ?? credential.refreshToken;
190
+ const fingerprint = token && typeof token === "string" ? Bun.hash(token).toString(16) : "anonymous";
191
+ const baseUrl = params.baseUrl ?? DEFAULT_ENDPOINT;
192
+ return `profile:${params.provider}:${fingerprint}:${baseUrl}`;
193
+ }
194
+
195
+ async function resolveEmail(
196
+ params: UsageFetchParams,
197
+ ctx: UsageFetchContext,
198
+ baseUrl: string,
199
+ headers: Record<string, string>,
200
+ ): Promise<string | undefined> {
201
+ if (params.credential.email) return params.credential.email;
202
+
203
+ const cacheKey = buildProfileCacheKey(params);
204
+ const cached = await ctx.cache.get(cacheKey);
205
+ const now = ctx.now();
206
+ if (cached && cached.expiresAt > now && cached.value?.metadata?.email) {
207
+ return cached.value.metadata.email as string;
208
+ }
209
+
210
+ const profile = await fetchProfile(baseUrl, headers, ctx, params.signal);
211
+ const email = profile?.account?.email;
212
+ if (email) {
213
+ const entry = {
214
+ value: { provider: params.provider, fetchedAt: now, limits: [], metadata: { email } },
215
+ expiresAt: now + PROFILE_CACHE_TTL_MS,
216
+ };
217
+ await ctx.cache.set(cacheKey, entry);
218
+ }
219
+ return email;
220
+ }
221
+
163
222
  function buildUsageAmount(utilization: number | undefined): UsageAmount | undefined {
164
223
  if (utilization === undefined) return undefined;
165
224
  const clamped = Math.min(Math.max(utilization, 0), 100);
@@ -329,7 +388,7 @@ async function fetchClaudeUsage(params: UsageFetchParams, ctx: UsageFetchContext
329
388
  if (limits.length === 0) return cachedValue;
330
389
  const identity = extractUsageIdentity(payload, orgId);
331
390
  const accountId = identity.accountId ?? credential.accountId;
332
- const email = identity.email ?? credential.email;
391
+ const email = identity.email ?? (await resolveEmail(params, ctx, baseUrl, headers));
333
392
 
334
393
  const report: UsageReport = {
335
394
  provider: params.provider,
package/src/usage/kimi.ts CHANGED
@@ -9,7 +9,7 @@ import type {
9
9
  UsageStatus,
10
10
  UsageWindow,
11
11
  } from "../usage";
12
- import { getKimiCommonHeaders } from "../utils/oauth/kimi";
12
+ import { getKimiCommonHeaders, refreshKimiToken } from "../utils/oauth/kimi";
13
13
 
14
14
  const DEFAULT_BASE_URL = "https://api.kimi.com/coding/v1";
15
15
  const USAGE_PATH = "usages";
@@ -259,13 +259,23 @@ export const kimiUsageProvider: UsageProvider = {
259
259
  const { credential } = params;
260
260
  if (credential.type !== "oauth") return null;
261
261
 
262
- const accessToken = credential.accessToken;
262
+ let accessToken = credential.accessToken;
263
263
  if (!accessToken) return null;
264
264
 
265
265
  const nowMs = ctx.now();
266
266
  if (credential.expiresAt !== undefined && credential.expiresAt <= nowMs) {
267
- ctx.logger?.warn("Kimi usage token expired", { provider: params.provider });
268
- return null;
267
+ if (!credential.refreshToken) {
268
+ ctx.logger?.warn("Kimi usage token expired, no refresh token", { provider: params.provider });
269
+ return null;
270
+ }
271
+ try {
272
+ ctx.logger?.debug("Kimi usage token expired, refreshing", { provider: params.provider });
273
+ const refreshed = await refreshKimiToken(credential.refreshToken);
274
+ accessToken = refreshed.access;
275
+ } catch (error) {
276
+ ctx.logger?.warn("Kimi usage token refresh failed", { provider: params.provider, error: String(error) });
277
+ return null;
278
+ }
269
279
  }
270
280
 
271
281
  const baseUrl = normalizeBaseUrl(params.baseUrl);