chatccc 0.2.187 → 0.2.189

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.
Files changed (71) hide show
  1. package/agent-prompts/claude_specific.md +45 -45
  2. package/agent-prompts/codex_specific.md +2 -2
  3. package/agent-prompts/cursor_specific.md +13 -13
  4. package/config.sample.json +5 -0
  5. package/im-skills/feishu-skill/receive-send-file.md +63 -63
  6. package/im-skills/feishu-skill/receive-send-image.md +24 -24
  7. package/im-skills/feishu-skill/skill.md +3 -3
  8. package/im-skills/wechat-file-skill/receive-send-file.md +38 -38
  9. package/im-skills/wechat-file-skill/send-file.mjs +83 -83
  10. package/im-skills/wechat-file-skill/skill.md +10 -10
  11. package/im-skills/wechat-image-skill/skill.md +10 -10
  12. package/im-skills/wechat-video-skill/receive-send-video.md +38 -38
  13. package/im-skills/wechat-video-skill/send-video.mjs +79 -79
  14. package/im-skills/wechat-video-skill/skill.md +10 -10
  15. package/package.json +1 -1
  16. package/scripts/postinstall-sharp-check.mjs +58 -58
  17. package/src/__tests__/agent-delegate-task-rpc.test.ts +165 -165
  18. package/src/__tests__/builtin-config.test.ts +33 -0
  19. package/src/__tests__/card-plain-text.test.ts +5 -5
  20. package/src/__tests__/cardkit.test.ts +60 -60
  21. package/src/__tests__/cards.test.ts +77 -77
  22. package/src/__tests__/chatgpt-subscription-rpc.test.ts +89 -89
  23. package/src/__tests__/chatgpt-subscription.test.ts +135 -135
  24. package/src/__tests__/chrome-devtools-guard.test.ts +165 -125
  25. package/src/__tests__/claude-adapter.test.ts +592 -592
  26. package/src/__tests__/codex-reset-actions.test.ts +146 -146
  27. package/src/__tests__/config-reload.test.ts +1 -0
  28. package/src/__tests__/config-sample.test.ts +11 -0
  29. package/src/__tests__/feishu-api.test.ts +60 -60
  30. package/src/__tests__/feishu-avatar.test.ts +180 -115
  31. package/src/__tests__/feishu-platform.test.ts +22 -22
  32. package/src/__tests__/format-message.test.ts +47 -47
  33. package/src/__tests__/orchestrator.test.ts +150 -2
  34. package/src/__tests__/privacy.test.ts +198 -198
  35. package/src/__tests__/raw-stream-log.test.ts +106 -106
  36. package/src/__tests__/session.test.ts +10 -0
  37. package/src/__tests__/shared-prefix.test.ts +36 -36
  38. package/src/__tests__/stream-state.test.ts +42 -42
  39. package/src/__tests__/web-ui.test.ts +209 -130
  40. package/src/adapters/claude-adapter.ts +566 -566
  41. package/src/adapters/claude-session-meta-store.ts +120 -120
  42. package/src/adapters/codex-adapter.ts +10 -6
  43. package/src/adapters/cursor-adapter.ts +46 -46
  44. package/src/adapters/raw-stream-log.ts +124 -124
  45. package/src/adapters/resource-monitor.ts +140 -140
  46. package/src/agent-delegate-task-rpc.ts +153 -153
  47. package/src/agent-delegate-task.ts +81 -81
  48. package/src/agent-stop-stuck.ts +129 -129
  49. package/src/builtin/cli.ts +189 -197
  50. package/src/builtin/index.ts +168 -167
  51. package/src/cards.ts +130 -89
  52. package/src/chatgpt-subscription-rpc.ts +27 -27
  53. package/src/chatgpt-subscription.ts +299 -299
  54. package/src/chrome-devtools-guard.ts +318 -242
  55. package/src/codex-reset-actions.ts +184 -184
  56. package/src/config.ts +38 -0
  57. package/src/feishu-api.ts +219 -190
  58. package/src/feishu-platform.ts +20 -20
  59. package/src/format-message.ts +293 -293
  60. package/src/index.ts +2 -2
  61. package/src/litellm-proxy.ts +374 -374
  62. package/src/orchestrator.ts +201 -9
  63. package/src/platform-adapter.ts +9 -1
  64. package/src/privacy.ts +118 -118
  65. package/src/session-chat-binding.ts +6 -6
  66. package/src/session-name.ts +8 -8
  67. package/src/session.ts +44 -16
  68. package/src/shared-prefix.ts +29 -29
  69. package/src/sim-platform.ts +20 -20
  70. package/src/turn-cards.ts +117 -117
  71. package/src/web-ui.ts +142 -24
package/src/feishu-api.ts CHANGED
@@ -20,6 +20,7 @@ import {
20
20
  config,
21
21
  } from "./config.ts";
22
22
  import { getCursorUsageSummary, type CursorUsageSummary } from "./cursor-usage.ts";
23
+ import type { ChatAvatarUsageHints } from "./platform-adapter.ts";
23
24
  import { applyPrivacy } from "./privacy.ts";
24
25
 
25
26
  // ---------------------------------------------------------------------------
@@ -302,10 +303,10 @@ const AVATAR_DIR = resolvePath(PROJECT_ROOT, "images", "avatars");
302
303
  const AVATAR_BADGE_DIR = resolvePath(AVATAR_DIR, "badges");
303
304
  const AVATAR_COMBINATIONS_DIR = resolvePath(AVATAR_DIR, "combinations");
304
305
  const AVATAR_KEY_CACHE_FILE = resolvePath(USER_DATA_DIR, "state", "avatar-image-keys.json");
305
- const CODEX_AUTH_FILE = resolvePath(homedir(), ".codex", "auth.json");
306
- const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
307
- const CODEX_RESET_CREDITS_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits";
308
- const CODEX_RESET_CONSUME_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits/consume";
306
+ const CODEX_AUTH_FILE = resolvePath(homedir(), ".codex", "auth.json");
307
+ const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
308
+ const CODEX_RESET_CREDITS_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits";
309
+ const CODEX_RESET_CONSUME_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits/consume";
309
310
  const AVATAR_SOURCES: Record<string, string> = {
310
311
  new: resolvePath(AVATAR_DIR, "status_new.png"),
311
312
  busy: resolvePath(AVATAR_DIR, "status_busy.png"),
@@ -322,31 +323,31 @@ const AVATAR_BADGE_MARGIN = 10;
322
323
  const CODEX_AVATAR_USAGE_STYLE_VERSION = "usage-ring-gray-consumed-v13";
323
324
  const CURSOR_AVATAR_USAGE_STYLE_VERSION = "usage-battery-v1";
324
325
 
325
- export interface CodexUsageBalance {
326
- usedPercent: number;
327
- remainingPercent: number;
328
- resetAtEpochSeconds: number | null;
329
- resetAfterSeconds: number | null;
330
- }
331
-
332
- export interface CodexRateLimitResetCredit {
333
- grantedAt: string | null;
334
- expiresAt: string;
335
- }
336
-
337
- export interface CodexUsageSummary {
338
- fiveHour: CodexUsageBalance;
339
- weekly: CodexUsageBalance | null;
340
- rateLimitResetCreditsAvailable: number | null;
341
- rateLimitResetCredits: CodexRateLimitResetCredit[] | null;
342
- }
343
-
344
- export type CodexResetConsumeCode = "reset" | "nothing_to_reset" | "no_credit" | "already_redeemed";
345
-
346
- export interface CodexResetConsumeResult {
347
- code: CodexResetConsumeCode;
348
- windowsReset: number;
349
- }
326
+ export interface CodexUsageBalance {
327
+ usedPercent: number;
328
+ remainingPercent: number;
329
+ resetAtEpochSeconds: number | null;
330
+ resetAfterSeconds: number | null;
331
+ }
332
+
333
+ export interface CodexRateLimitResetCredit {
334
+ grantedAt: string | null;
335
+ expiresAt: string;
336
+ }
337
+
338
+ export interface CodexUsageSummary {
339
+ fiveHour: CodexUsageBalance;
340
+ weekly: CodexUsageBalance | null;
341
+ rateLimitResetCreditsAvailable: number | null;
342
+ rateLimitResetCredits: CodexRateLimitResetCredit[] | null;
343
+ }
344
+
345
+ export type CodexResetConsumeCode = "reset" | "nothing_to_reset" | "no_credit" | "already_redeemed";
346
+
347
+ export interface CodexResetConsumeResult {
348
+ code: CodexResetConsumeCode;
349
+ windowsReset: number;
350
+ }
350
351
 
351
352
  const avatarKeyCache = new Map<string, string>();
352
353
  let avatarKeyCacheLoaded = false;
@@ -427,167 +428,172 @@ function usageBalanceFromWindow(raw: Record<string, unknown>, fieldName: string)
427
428
  };
428
429
  }
429
430
 
430
- function parseOptionalUsageWindow(rateLimit: Record<string, unknown>, keys: string[]): CodexUsageBalance | null {
431
+ function parseOptionalUsageWindow(rateLimit: Record<string, unknown>, keys: string[]): CodexUsageBalance | null {
431
432
  for (const key of keys) {
432
433
  const raw = rateLimit[key];
433
434
  if (!raw || typeof raw !== "object") continue;
434
435
  if ((raw as { used_percent?: unknown }).used_percent === undefined) continue;
435
436
  return usageBalanceFromWindow(raw as Record<string, unknown>, `rate_limit.${key}`);
436
437
  }
437
- return null;
438
- }
439
-
440
- function parseRateLimitResetCredits(data: Record<string, unknown>): number | null {
441
- const raw = data.rate_limit_reset_credits;
442
- if (!raw || typeof raw !== "object") return null;
443
- const availableCount = Number((raw as { available_count?: unknown }).available_count);
444
- if (!Number.isFinite(availableCount)) return null;
445
- return Math.max(0, Math.trunc(availableCount));
446
- }
447
-
448
- interface CodexAuth {
449
- accessToken: string;
450
- accountId?: string;
451
- }
452
-
453
- async function getCodexAuth(): Promise<CodexAuth | null> {
454
- try {
455
- const raw = await readFile(CODEX_AUTH_FILE, "utf-8");
456
- const parsed = JSON.parse(raw) as { tokens?: { access_token?: unknown; account_id?: unknown } };
457
- const token = parsed.tokens?.access_token;
458
- if (typeof token !== "string" || !token.trim()) return null;
459
- const accountId = parsed.tokens?.account_id;
460
- return {
461
- accessToken: token,
462
- accountId: typeof accountId === "string" && accountId.trim() ? accountId : undefined,
463
- };
464
- } catch {
465
- return null;
466
- }
467
- }
468
-
469
- async function getCodexAccessToken(): Promise<string | null> {
470
- return (await getCodexAuth())?.accessToken ?? null;
471
- }
472
-
473
- function codexAuthHeaders(auth: CodexAuth): Record<string, string> {
474
- const headers: Record<string, string> = {
475
- Authorization: `Bearer ${auth.accessToken}`,
476
- "OpenAI-Beta": "codex-1",
477
- originator: "Codex Desktop",
478
- };
479
- if (auth.accountId) headers["ChatGPT-Account-ID"] = auth.accountId;
480
- return headers;
481
- }
482
-
483
- function parseCodexResetCreditDetails(data: Record<string, unknown>): {
484
- availableCount: number | null;
485
- availableCredits: CodexRateLimitResetCredit[];
486
- } {
487
- const availableCount = Number(data.available_count);
488
- const credits = Array.isArray(data.credits) ? data.credits : [];
489
- return {
490
- availableCount: Number.isFinite(availableCount) ? Math.max(0, Math.trunc(availableCount)) : null,
491
- availableCredits: credits.flatMap((raw) => {
492
- if (!raw || typeof raw !== "object") return [];
493
- const credit = raw as { status?: unknown; granted_at?: unknown; expires_at?: unknown };
494
- if (credit.status !== "available") return [];
495
- if (typeof credit.expires_at !== "string" || !credit.expires_at.trim()) return [];
496
- return [{
497
- grantedAt: typeof credit.granted_at === "string" && credit.granted_at.trim() ? credit.granted_at : null,
498
- expiresAt: credit.expires_at,
499
- }];
500
- }),
501
- };
502
- }
503
-
504
- async function fetchCodexRateLimitResetCredits(auth: CodexAuth): Promise<{
505
- availableCount: number | null;
506
- availableCredits: CodexRateLimitResetCredit[];
507
- } | null> {
508
- try {
509
- const resp = await fetch(CODEX_RESET_CREDITS_URL, {
510
- headers: codexAuthHeaders(auth),
511
- });
512
- const text = await resp.text();
513
- if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${text.slice(0, 160)}`);
514
- return parseCodexResetCreditDetails(JSON.parse(text) as Record<string, unknown>);
515
- } catch (err) {
516
- console.warn(`[Codex] reset credits lookup failed: ${(err as Error).message}`);
517
- return null;
518
- }
519
- }
520
-
521
- export async function getCodexUsageSummary(): Promise<CodexUsageSummary> {
522
- const auth = await getCodexAuth();
523
- if (!auth) throw new Error("missing ~/.codex/auth.json access token");
524
-
525
- const resetCreditsPromise = fetchCodexRateLimitResetCredits(auth);
526
-
527
- const resp = await fetch(CODEX_USAGE_URL, {
528
- headers: { Authorization: `Bearer ${auth.accessToken}` },
529
- });
530
- const text = await resp.text();
531
- if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${text.slice(0, 160)}`);
532
-
533
- const data = JSON.parse(text) as {
534
- rate_limit?: Record<string, unknown>;
535
- rate_limit_reset_credits?: unknown;
536
- };
537
- const rateLimit = data.rate_limit;
538
- if (!rateLimit || typeof rateLimit !== "object") throw new Error("missing rate_limit");
539
-
540
- const fiveHour = parseOptionalUsageWindow(rateLimit, ["primary_window"]);
541
- if (!fiveHour) throw new Error("missing rate_limit.primary_window.used_percent");
542
- const resetCredits = await resetCreditsPromise;
543
-
544
- return {
545
- fiveHour,
546
- weekly: parseOptionalUsageWindow(rateLimit, [
547
- "secondary_window",
548
- "weekly_window",
549
- "week_window",
550
- "long_window",
551
- ]),
552
- rateLimitResetCreditsAvailable: resetCredits?.availableCount ?? parseRateLimitResetCredits(data),
553
- rateLimitResetCredits: resetCredits?.availableCredits ?? null,
554
- };
555
- }
556
-
557
- export async function consumeCodexRateLimitResetCredit(redeemRequestId: string): Promise<CodexResetConsumeResult> {
558
- if (!redeemRequestId.trim()) throw new Error("missing redeem_request_id");
559
- const token = await getCodexAccessToken();
560
- if (!token) throw new Error("missing ~/.codex/auth.json access token");
561
-
562
- const resp = await fetch(CODEX_RESET_CONSUME_URL, {
563
- method: "POST",
564
- headers: {
565
- Authorization: `Bearer ${token}`,
566
- "Content-Type": "application/json",
567
- },
568
- body: JSON.stringify({ redeem_request_id: redeemRequestId }),
569
- });
570
- const text = await resp.text();
571
- if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${text.slice(0, 160)}`);
572
-
573
- const data = JSON.parse(text) as { code?: unknown; windows_reset?: unknown };
574
- const code = data.code;
575
- if (
576
- code !== "reset"
577
- && code !== "nothing_to_reset"
578
- && code !== "no_credit"
579
- && code !== "already_redeemed"
580
- ) {
581
- throw new Error("missing or unknown reset result code");
582
- }
583
- const windowsReset = Number(data.windows_reset);
584
- return {
585
- code,
586
- windowsReset: Number.isFinite(windowsReset) ? Math.max(0, Math.trunc(windowsReset)) : 0,
587
- };
588
- }
589
-
590
- async function fetchCodexAvatarUsage(): Promise<CodexUsageSummary | null> {
438
+ return null;
439
+ }
440
+
441
+ function parseRateLimitResetCredits(data: Record<string, unknown>): number | null {
442
+ const raw = data.rate_limit_reset_credits;
443
+ if (!raw || typeof raw !== "object") return null;
444
+ const availableCount = Number((raw as { available_count?: unknown }).available_count);
445
+ if (!Number.isFinite(availableCount)) return null;
446
+ return Math.max(0, Math.trunc(availableCount));
447
+ }
448
+
449
+ interface CodexAuth {
450
+ accessToken: string;
451
+ accountId?: string;
452
+ }
453
+
454
+ async function getCodexAuth(): Promise<CodexAuth | null> {
455
+ try {
456
+ const raw = await readFile(CODEX_AUTH_FILE, "utf-8");
457
+ const parsed = JSON.parse(raw) as { tokens?: { access_token?: unknown; account_id?: unknown } };
458
+ const token = parsed.tokens?.access_token;
459
+ if (typeof token !== "string" || !token.trim()) return null;
460
+ const accountId = parsed.tokens?.account_id;
461
+ return {
462
+ accessToken: token,
463
+ accountId: typeof accountId === "string" && accountId.trim() ? accountId : undefined,
464
+ };
465
+ } catch {
466
+ return null;
467
+ }
468
+ }
469
+
470
+ async function getCodexAccessToken(): Promise<string | null> {
471
+ return (await getCodexAuth())?.accessToken ?? null;
472
+ }
473
+
474
+ function codexAuthHeaders(auth: CodexAuth): Record<string, string> {
475
+ const headers: Record<string, string> = {
476
+ Authorization: `Bearer ${auth.accessToken}`,
477
+ "OpenAI-Beta": "codex-1",
478
+ originator: "Codex Desktop",
479
+ };
480
+ if (auth.accountId) headers["ChatGPT-Account-ID"] = auth.accountId;
481
+ return headers;
482
+ }
483
+
484
+ function parseCodexResetCreditDetails(data: Record<string, unknown>): {
485
+ availableCount: number | null;
486
+ availableCredits: CodexRateLimitResetCredit[];
487
+ } {
488
+ const availableCount = Number(data.available_count);
489
+ const credits = Array.isArray(data.credits) ? data.credits : [];
490
+ return {
491
+ availableCount: Number.isFinite(availableCount) ? Math.max(0, Math.trunc(availableCount)) : null,
492
+ availableCredits: credits.flatMap((raw) => {
493
+ if (!raw || typeof raw !== "object") return [];
494
+ const credit = raw as { status?: unknown; granted_at?: unknown; expires_at?: unknown };
495
+ if (credit.status !== "available") return [];
496
+ if (typeof credit.expires_at !== "string" || !credit.expires_at.trim()) return [];
497
+ return [{
498
+ grantedAt: typeof credit.granted_at === "string" && credit.granted_at.trim() ? credit.granted_at : null,
499
+ expiresAt: credit.expires_at,
500
+ }];
501
+ }),
502
+ };
503
+ }
504
+
505
+ async function fetchCodexRateLimitResetCredits(auth: CodexAuth): Promise<{
506
+ availableCount: number | null;
507
+ availableCredits: CodexRateLimitResetCredit[];
508
+ } | null> {
509
+ try {
510
+ const resp = await fetch(CODEX_RESET_CREDITS_URL, {
511
+ headers: codexAuthHeaders(auth),
512
+ });
513
+ const text = await resp.text();
514
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${text.slice(0, 160)}`);
515
+ return parseCodexResetCreditDetails(JSON.parse(text) as Record<string, unknown>);
516
+ } catch (err) {
517
+ console.warn(`[Codex] reset credits lookup failed: ${(err as Error).message}`);
518
+ return null;
519
+ }
520
+ }
521
+
522
+ export async function getCodexUsageSummary(): Promise<CodexUsageSummary> {
523
+ const auth = await getCodexAuth();
524
+ if (!auth) throw new Error("missing ~/.codex/auth.json access token");
525
+
526
+ const resetCreditsPromise = fetchCodexRateLimitResetCredits(auth);
527
+
528
+ const resp = await fetch(CODEX_USAGE_URL, {
529
+ headers: { Authorization: `Bearer ${auth.accessToken}` },
530
+ });
531
+ const text = await resp.text();
532
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${text.slice(0, 160)}`);
533
+
534
+ const data = JSON.parse(text) as {
535
+ rate_limit?: Record<string, unknown>;
536
+ rate_limit_reset_credits?: unknown;
537
+ };
538
+ const rateLimit = data.rate_limit;
539
+ if (!rateLimit || typeof rateLimit !== "object") throw new Error("missing rate_limit");
540
+
541
+ const fiveHour = parseOptionalUsageWindow(rateLimit, ["primary_window"]);
542
+ if (!fiveHour) throw new Error("missing rate_limit.primary_window.used_percent");
543
+ const resetCredits = await resetCreditsPromise;
544
+
545
+ return {
546
+ fiveHour,
547
+ weekly: parseOptionalUsageWindow(rateLimit, [
548
+ "secondary_window",
549
+ "weekly_window",
550
+ "week_window",
551
+ "long_window",
552
+ ]),
553
+ rateLimitResetCreditsAvailable: resetCredits?.availableCount ?? parseRateLimitResetCredits(data),
554
+ rateLimitResetCredits: resetCredits?.availableCredits ?? null,
555
+ };
556
+ }
557
+
558
+ export async function consumeCodexRateLimitResetCredit(redeemRequestId: string): Promise<CodexResetConsumeResult> {
559
+ if (!redeemRequestId.trim()) throw new Error("missing redeem_request_id");
560
+ const token = await getCodexAccessToken();
561
+ if (!token) throw new Error("missing ~/.codex/auth.json access token");
562
+
563
+ const resp = await fetch(CODEX_RESET_CONSUME_URL, {
564
+ method: "POST",
565
+ headers: {
566
+ Authorization: `Bearer ${token}`,
567
+ "Content-Type": "application/json",
568
+ },
569
+ body: JSON.stringify({ redeem_request_id: redeemRequestId }),
570
+ });
571
+ const text = await resp.text();
572
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${text.slice(0, 160)}`);
573
+
574
+ const data = JSON.parse(text) as { code?: unknown; windows_reset?: unknown };
575
+ const code = data.code;
576
+ if (
577
+ code !== "reset"
578
+ && code !== "nothing_to_reset"
579
+ && code !== "no_credit"
580
+ && code !== "already_redeemed"
581
+ ) {
582
+ throw new Error("missing or unknown reset result code");
583
+ }
584
+ const windowsReset = Number(data.windows_reset);
585
+ return {
586
+ code,
587
+ windowsReset: Number.isFinite(windowsReset) ? Math.max(0, Math.trunc(windowsReset)) : 0,
588
+ };
589
+ }
590
+
591
+ async function resolveCodexAvatarUsage(usageHint?: CodexUsageSummary | null): Promise<CodexUsageSummary | null> {
592
+ if (usageHint !== undefined) {
593
+ if (!usageHint?.weekly) return null;
594
+ return usageHint;
595
+ }
596
+
591
597
  try {
592
598
  const summary = await getCodexUsageSummary();
593
599
  if (!summary.weekly) throw new Error("missing weekly usage window");
@@ -611,7 +617,17 @@ function cursorAvatarBatteryPercentFromUsage(summary: CursorUsageSummary): numbe
611
617
  return clampPercent(100 - apiPercentUsed);
612
618
  }
613
619
 
614
- async function fetchCursorAvatarBatteryPercent(): Promise<number | null> {
620
+ async function resolveCursorAvatarBatteryPercent(usageHint?: CursorUsageSummary | null): Promise<number | null> {
621
+ if (usageHint !== undefined) {
622
+ if (!usageHint) return null;
623
+ try {
624
+ return cursorAvatarBatteryPercentFromUsage(usageHint);
625
+ } catch (err) {
626
+ console.warn(`[${ts()}] [AVATAR] Cursor usage unavailable, using plain avatar: ${(err as Error).message}`);
627
+ return null;
628
+ }
629
+ }
630
+
615
631
  try {
616
632
  return cursorAvatarBatteryPercentFromUsage(await getCursorUsageSummary());
617
633
  } catch (err) {
@@ -814,12 +830,19 @@ async function uploadImage(
814
830
  return data.data!.image_key!;
815
831
  }
816
832
 
817
- async function getOrUploadAvatarKey(token: string, tool: string, status: string): Promise<string> {
833
+ async function getOrUploadAvatarKey(
834
+ token: string,
835
+ tool: string,
836
+ status: string,
837
+ usageHints: ChatAvatarUsageHints = {},
838
+ ): Promise<string> {
818
839
  await loadAvatarKeyCache();
819
840
  const normalizedTool = normalizeAvatarTool(tool);
820
841
  const normalizedStatus = normalizeAvatarStatus(status);
821
- const codexUsage = normalizedTool === "codex" ? await fetchCodexAvatarUsage() : null;
822
- const cursorBatteryPercent = normalizedTool === "cursor" ? await fetchCursorAvatarBatteryPercent() : null;
842
+ const codexUsage = normalizedTool === "codex" ? await resolveCodexAvatarUsage(usageHints.codexUsage) : null;
843
+ const cursorBatteryPercent = normalizedTool === "cursor"
844
+ ? await resolveCursorAvatarBatteryPercent(usageHints.cursorUsage)
845
+ : null;
823
846
  const keyName = avatarCacheKey(normalizedTool, normalizedStatus, codexUsage, cursorBatteryPercent);
824
847
  const cached = avatarKeyCache.get(keyName);
825
848
  if (cached) return cached;
@@ -832,9 +855,15 @@ async function getOrUploadAvatarKey(token: string, tool: string, status: string)
832
855
  return key;
833
856
  }
834
857
 
835
- export async function setChatAvatar(token: string, chatId: string, tool: string, status: string): Promise<void> {
858
+ export async function setChatAvatar(
859
+ token: string,
860
+ chatId: string,
861
+ tool: string,
862
+ status: string,
863
+ usageHints: ChatAvatarUsageHints = {},
864
+ ): Promise<void> {
836
865
  try {
837
- const avatarKey = await getOrUploadAvatarKey(token, tool, status);
866
+ const avatarKey = await getOrUploadAvatarKey(token, tool, status, usageHints);
838
867
  const resp = await fetch(`${BASE_URL}/im/v1/chats/${chatId}`, {
839
868
  method: "PUT",
840
869
  headers: {
@@ -29,10 +29,10 @@ export interface FeishuPlatform {
29
29
  updateChatInfo: typeof realApi.updateChatInfo;
30
30
  getChatInfo: typeof realApi.getChatInfo;
31
31
  disbandChat: typeof realApi.disbandChat;
32
- setChatAvatar: typeof realApi.setChatAvatar;
33
- getCodexUsageSummary: typeof realApi.getCodexUsageSummary;
34
- consumeCodexRateLimitResetCredit: typeof realApi.consumeCodexRateLimitResetCredit;
35
- getOrDownloadImage: typeof realApi.getOrDownloadImage;
32
+ setChatAvatar: typeof realApi.setChatAvatar;
33
+ getCodexUsageSummary: typeof realApi.getCodexUsageSummary;
34
+ consumeCodexRateLimitResetCredit: typeof realApi.consumeCodexRateLimitResetCredit;
35
+ getOrDownloadImage: typeof realApi.getOrDownloadImage;
36
36
  verifyAllPermissions: typeof realApi.verifyAllPermissions;
37
37
  reportPermissionResults: typeof realApi.reportPermissionResults;
38
38
  extractSessionInfo: typeof realApi.extractSessionInfo;
@@ -110,21 +110,21 @@ export function disbandChat(...args: Parameters<typeof realApi.disbandChat>): Re
110
110
  return _impl.disbandChat(...args);
111
111
  }
112
112
 
113
- export function setChatAvatar(...args: Parameters<typeof realApi.setChatAvatar>): ReturnType<typeof realApi.setChatAvatar> {
114
- return _impl.setChatAvatar(...args);
115
- }
116
-
117
- export function getCodexUsageSummary(...args: Parameters<typeof realApi.getCodexUsageSummary>): ReturnType<typeof realApi.getCodexUsageSummary> {
118
- return _impl.getCodexUsageSummary(...args);
119
- }
120
-
121
- export function consumeCodexRateLimitResetCredit(...args: Parameters<typeof realApi.consumeCodexRateLimitResetCredit>): ReturnType<typeof realApi.consumeCodexRateLimitResetCredit> {
122
- return _impl.consumeCodexRateLimitResetCredit(...args);
123
- }
124
-
125
- export function getOrDownloadImage(...args: Parameters<typeof realApi.getOrDownloadImage>): ReturnType<typeof realApi.getOrDownloadImage> {
126
- return _impl.getOrDownloadImage(...args);
127
- }
113
+ export function setChatAvatar(...args: Parameters<typeof realApi.setChatAvatar>): ReturnType<typeof realApi.setChatAvatar> {
114
+ return _impl.setChatAvatar(...args);
115
+ }
116
+
117
+ export function getCodexUsageSummary(...args: Parameters<typeof realApi.getCodexUsageSummary>): ReturnType<typeof realApi.getCodexUsageSummary> {
118
+ return _impl.getCodexUsageSummary(...args);
119
+ }
120
+
121
+ export function consumeCodexRateLimitResetCredit(...args: Parameters<typeof realApi.consumeCodexRateLimitResetCredit>): ReturnType<typeof realApi.consumeCodexRateLimitResetCredit> {
122
+ return _impl.consumeCodexRateLimitResetCredit(...args);
123
+ }
124
+
125
+ export function getOrDownloadImage(...args: Parameters<typeof realApi.getOrDownloadImage>): ReturnType<typeof realApi.getOrDownloadImage> {
126
+ return _impl.getOrDownloadImage(...args);
127
+ }
128
128
 
129
129
  export function verifyAllPermissions(...args: Parameters<typeof realApi.verifyAllPermissions>): ReturnType<typeof realApi.verifyAllPermissions> {
130
130
  return _impl.verifyAllPermissions(...args);
@@ -156,4 +156,4 @@ export function sendRestartCard(...args: Parameters<typeof realApi.sendRestartCa
156
156
 
157
157
  export function getMergeForwardMessages(...args: Parameters<typeof realApi.getMergeForwardMessages>): ReturnType<typeof realApi.getMergeForwardMessages> {
158
158
  return _impl.getMergeForwardMessages(...args);
159
- }
159
+ }