@ccpocket/bridge 1.89.0 → 1.91.0

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.
@@ -0,0 +1,13 @@
1
+ import type { UsageInfo } from "./usage.js";
2
+ /**
3
+ * Map the experimental SDK usage response to UsageInfo.
4
+ * Returns null when plan limits do not apply or no supported window exists,
5
+ * so callers can omit Claude instead of showing an empty card.
6
+ */
7
+ export declare function mapClaudeUsageResponse(response: unknown): UsageInfo | null;
8
+ /**
9
+ * Fetch Claude plan usage. Resolves to null when Claude plan limits are not
10
+ * applicable (not configured, API key/Bedrock/Vertex auth, OAuth not opted in,
11
+ * or an SDK without the experimental API). Never rejects.
12
+ */
13
+ export declare function fetchClaudeUsage(): Promise<UsageInfo | null>;
@@ -0,0 +1,139 @@
1
+ import { homedir } from "node:os";
2
+ import { query } from "@anthropic-ai/claude-agent-sdk";
3
+ import { canStartClaudeSdk, isClaudeOAuthOptInEnabled, } from "./sdk-process.js";
4
+ // Claude plan usage is read only through the Claude Agent SDK's experimental
5
+ // `/usage` API. This adapter isolates the unstable method name and response
6
+ // shape so upstream changes stay contained here. It must never read
7
+ // credentials or call claude.ai endpoints directly; the SDK resolves auth.
8
+ const CLAUDE_USAGE_TIMEOUT_MS = 15_000;
9
+ async function* createIdleUserMessageStream() {
10
+ await new Promise(() => { });
11
+ }
12
+ function isRecord(value) {
13
+ return typeof value === "object" && value !== null;
14
+ }
15
+ function mapClaudeWindow(value) {
16
+ if (!isRecord(value))
17
+ return null;
18
+ const { utilization, resets_at: resetsAt } = value;
19
+ if (typeof utilization !== "number" || !Number.isFinite(utilization)) {
20
+ return null;
21
+ }
22
+ if (typeof resetsAt !== "string" || Number.isNaN(Date.parse(resetsAt))) {
23
+ return null;
24
+ }
25
+ return {
26
+ utilization: Math.min(100, Math.max(0, utilization)),
27
+ resetsAt: new Date(resetsAt).toISOString(),
28
+ };
29
+ }
30
+ /**
31
+ * Map the experimental SDK usage response to UsageInfo.
32
+ * Returns null when plan limits do not apply or no supported window exists,
33
+ * so callers can omit Claude instead of showing an empty card.
34
+ */
35
+ export function mapClaudeUsageResponse(response) {
36
+ if (!isRecord(response))
37
+ return null;
38
+ if (response.rate_limits_available !== true)
39
+ return null;
40
+ const rateLimits = response.rate_limits;
41
+ if (!isRecord(rateLimits))
42
+ return null;
43
+ const fiveHour = mapClaudeWindow(rateLimits.five_hour);
44
+ const sevenDay = mapClaudeWindow(rateLimits.seven_day);
45
+ if (!fiveHour && !sevenDay)
46
+ return null;
47
+ return { provider: "claude", fiveHour, sevenDay };
48
+ }
49
+ class ClaudeUsageUnavailable extends Error {
50
+ }
51
+ function withTimeout(promise, ms) {
52
+ let timer;
53
+ const timeout = new Promise((_, reject) => {
54
+ timer = setTimeout(() => reject(new Error(`timed out after ${ms}ms`)), ms);
55
+ });
56
+ return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
57
+ }
58
+ async function readClaudeUsage() {
59
+ // Same preflight as Claude sessions: without an explicit credential,
60
+ // Bedrock, or the OAuth opt-in, Claude is simply not configured here.
61
+ if (!canStartClaudeSdk())
62
+ return null;
63
+ const queryInstance = query({
64
+ prompt: createIdleUserMessageStream(),
65
+ options: {
66
+ cwd: homedir(),
67
+ permissionMode: "default",
68
+ settingSources: ["user"],
69
+ stderr: (data) => {
70
+ const trimmed = data.trim();
71
+ if (trimmed)
72
+ console.error(`[claude-usage:stderr] ${trimmed}`);
73
+ },
74
+ },
75
+ });
76
+ const drain = (async () => {
77
+ try {
78
+ for await (const _message of queryInstance) {
79
+ // Drain initialization/status messages so control requests resolve.
80
+ }
81
+ }
82
+ catch {
83
+ // close() can reject the iterator; the usage result is handled below.
84
+ }
85
+ })();
86
+ try {
87
+ const response = await withTimeout((async () => {
88
+ const initialization = await queryInstance.initializationResult();
89
+ if (initialization.account.apiKeySource === "oauth" &&
90
+ !isClaudeOAuthOptInEnabled()) {
91
+ throw new ClaudeUsageUnavailable();
92
+ }
93
+ const getUsage = queryInstance
94
+ .usage_EXPERIMENTAL_MAY_CHANGE_DO_NOT_RELY_ON_THIS_API_YET;
95
+ if (typeof getUsage !== "function") {
96
+ throw new ClaudeUsageUnavailable();
97
+ }
98
+ // skipBehaviors avoids scanning every transcript from the last week.
99
+ return getUsage.call(queryInstance, { skipBehaviors: true });
100
+ })(), CLAUDE_USAGE_TIMEOUT_MS);
101
+ return mapClaudeUsageResponse(response);
102
+ }
103
+ catch (err) {
104
+ if (err instanceof ClaudeUsageUnavailable)
105
+ return null;
106
+ return {
107
+ provider: "claude",
108
+ fiveHour: null,
109
+ sevenDay: null,
110
+ error: `Failed to fetch Claude usage: ${err instanceof Error ? err.message : String(err)}`,
111
+ };
112
+ }
113
+ finally {
114
+ queryInstance.close();
115
+ void drain;
116
+ }
117
+ }
118
+ // Share concurrent reads (launch/resume/refresh can overlap), but never cache
119
+ // a completed snapshot so a manual refresh always queries fresh data.
120
+ let pendingClaudeUsage = null;
121
+ /**
122
+ * Fetch Claude plan usage. Resolves to null when Claude plan limits are not
123
+ * applicable (not configured, API key/Bedrock/Vertex auth, OAuth not opted in,
124
+ * or an SDK without the experimental API). Never rejects.
125
+ */
126
+ export function fetchClaudeUsage() {
127
+ pendingClaudeUsage ??= readClaudeUsage()
128
+ .catch((err) => ({
129
+ provider: "claude",
130
+ fiveHour: null,
131
+ sevenDay: null,
132
+ error: `Failed to fetch Claude usage: ${err instanceof Error ? err.message : String(err)}`,
133
+ }))
134
+ .finally(() => {
135
+ pendingClaudeUsage = null;
136
+ });
137
+ return pendingClaudeUsage;
138
+ }
139
+ //# sourceMappingURL=claude-usage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-usage.js","sourceRoot":"","sources":["../src/claude-usage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,KAAK,EAAuB,MAAM,gCAAgC,CAAC;AAC5E,OAAO,EACL,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,kBAAkB,CAAC;AAG1B,6EAA6E;AAC7E,4EAA4E;AAC5E,oEAAoE;AACpE,2EAA2E;AAE3E,MAAM,uBAAuB,GAAG,MAAM,CAAC;AAavC,KAAK,SAAS,CAAC,CAAC,2BAA2B;IACzC,MAAM,IAAI,OAAO,CAAQ,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;AACrC,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC;AACrD,CAAC;AAED,SAAS,eAAe,CAAC,KAAc;IACrC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAClC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IACnD,IAAI,OAAO,WAAW,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QACrE,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;QACvE,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO;QACL,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;QACpD,QAAQ,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE;KAC3C,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAAiB;IACtD,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IACrC,IAAI,QAAQ,CAAC,qBAAqB,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACzD,MAAM,UAAU,GAAG,QAAQ,CAAC,WAAW,CAAC;IACxC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IAEvC,MAAM,QAAQ,GAAG,eAAe,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IACvD,MAAM,QAAQ,GAAG,eAAe,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IACvD,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IACxC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;AACpD,CAAC;AAED,MAAM,sBAAuB,SAAQ,KAAK;CAAG;AAE7C,SAAS,WAAW,CAAI,OAAmB,EAAE,EAAU;IACrD,IAAI,KAAiC,CAAC;IACtC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE;QAC/C,KAAK,GAAG,UAAU,CAChB,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,mBAAmB,EAAE,IAAI,CAAC,CAAC,EAClD,EAAE,CACH,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;AAC7E,CAAC;AAED,KAAK,UAAU,eAAe;IAC5B,qEAAqE;IACrE,sEAAsE;IACtE,IAAI,CAAC,iBAAiB,EAAE;QAAE,OAAO,IAAI,CAAC;IAEtC,MAAM,aAAa,GAAG,KAAK,CAAC;QAC1B,MAAM,EAAE,2BAA2B,EAAE;QACrC,OAAO,EAAE;YACP,GAAG,EAAE,OAAO,EAAE;YACd,cAAc,EAAE,SAAS;YACzB,cAAc,EAAE,CAAC,MAAM,CAAC;YACxB,MAAM,EAAE,CAAC,IAAY,EAAE,EAAE;gBACvB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;gBAC5B,IAAI,OAAO;oBAAE,OAAO,CAAC,KAAK,CAAC,yBAAyB,OAAO,EAAE,CAAC,CAAC;YACjE,CAAC;SACF;KACF,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,CAAC,KAAK,IAAI,EAAE;QACxB,IAAI,CAAC;YACH,IAAI,KAAK,EAAE,MAAM,QAAQ,IAAI,aAAa,EAAE,CAAC;gBAC3C,oEAAoE;YACtE,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,sEAAsE;QACxE,CAAC;IACH,CAAC,CAAC,EAAE,CAAC;IAEL,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,WAAW,CAChC,CAAC,KAAK,IAAI,EAAE;YACV,MAAM,cAAc,GAAG,MAAM,aAAa,CAAC,oBAAoB,EAAE,CAAC;YAClE,IACE,cAAc,CAAC,OAAO,CAAC,YAAY,KAAK,OAAO;gBAC/C,CAAC,yBAAyB,EAAE,EAC5B,CAAC;gBACD,MAAM,IAAI,sBAAsB,EAAE,CAAC;YACrC,CAAC;YACD,MAAM,QAAQ,GAAI,aAAwC;iBACvD,yDAAyD,CAAC;YAC7D,IAAI,OAAO,QAAQ,KAAK,UAAU,EAAE,CAAC;gBACnC,MAAM,IAAI,sBAAsB,EAAE,CAAC;YACrC,CAAC;YACD,qEAAqE;YACrE,OAAO,QAAQ,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/D,CAAC,CAAC,EAAE,EACJ,uBAAuB,CACxB,CAAC;QACF,OAAO,sBAAsB,CAAC,QAAQ,CAAC,CAAC;IAC1C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,sBAAsB;YAAE,OAAO,IAAI,CAAC;QACvD,OAAO;YACL,QAAQ,EAAE,QAAQ;YAClB,QAAQ,EAAE,IAAI;YACd,QAAQ,EAAE,IAAI;YACd,KAAK,EAAE,iCAAiC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;SAC3F,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,aAAa,CAAC,KAAK,EAAE,CAAC;QACtB,KAAK,KAAK,CAAC;IACb,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,sEAAsE;AACtE,IAAI,kBAAkB,GAAqC,IAAI,CAAC;AAEhE;;;;GAIG;AACH,MAAM,UAAU,gBAAgB;IAC9B,kBAAkB,KAAK,eAAe,EAAE;SACrC,KAAK,CAAC,CAAC,GAAY,EAAa,EAAE,CAAC,CAAC;QACnC,QAAQ,EAAE,QAAQ;QAClB,QAAQ,EAAE,IAAI;QACd,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,iCAAiC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;KAC3F,CAAC,CAAC;SACF,OAAO,CAAC,GAAG,EAAE;QACZ,kBAAkB,GAAG,IAAI,CAAC;IAC5B,CAAC,CAAC,CAAC;IACL,OAAO,kBAAkB,CAAC;AAC5B,CAAC"}
@@ -107,6 +107,8 @@ export interface CodexModelMetadata {
107
107
  }
108
108
  export declare class CodexProcess extends EventEmitter<CodexProcessEvents> {
109
109
  private transport;
110
+ private reloadingProject;
111
+ private launchOptions;
110
112
  private _status;
111
113
  private _threadId;
112
114
  private _agentNickname;
@@ -264,6 +266,8 @@ export declare class CodexProcess extends EventEmitter<CodexProcessEvents> {
264
266
  listAvailableModelMetadata(): Promise<CodexModelMetadata[]>;
265
267
  start(projectPath: string, options?: CodexStartOptions): void;
266
268
  initializeOnly(projectPath: string, timeoutMs?: number): Promise<void>;
269
+ trustProject(projectPath: string): Promise<void>;
270
+ trustProjectAndReload(projectPath: string): Promise<void>;
267
271
  readRateLimits(timeoutMs?: number): Promise<CodexRateLimitsResponse>;
268
272
  waitUntilReady(): Promise<void>;
269
273
  stop(): void;
@@ -1,3 +1,4 @@
1
+ import { readCodexAppServerMode } from "./codex-app-server-config.js";
1
2
  import { CodexRecovery } from "./codex-recovery.js";
2
3
  import { EventEmitter } from "node:events";
3
4
  import { randomUUID } from "node:crypto";
@@ -63,6 +64,8 @@ function codexAppServerStartError(err) {
63
64
  }
64
65
  export class CodexProcess extends EventEmitter {
65
66
  transport = null;
67
+ reloadingProject = false;
68
+ launchOptions;
66
69
  _status = "starting";
67
70
  _threadId = null;
68
71
  _agentNickname = null;
@@ -140,7 +143,7 @@ export class CodexProcess extends EventEmitter {
140
143
  return this._status;
141
144
  }
142
145
  get isWaitingForInput() {
143
- return this.inputResolve !== null;
146
+ return !this.reloadingProject && this.inputResolve !== null;
144
147
  }
145
148
  getMessageModel() {
146
149
  return sanitizeCodexModel(this.startModel) ?? "";
@@ -289,7 +292,7 @@ export class CodexProcess extends EventEmitter {
289
292
  cancelRecovery() { this.manualInputVersion++; this.recovery.cancel(); }
290
293
  noteManualInput() { this.manualInputVersion++; this.recovery.manualInput(); }
291
294
  canDispatchRecovery() {
292
- return !this.stopped && this._threadId !== null && this.inputResolve !== null &&
295
+ return !this.stopped && !this.reloadingProject && this._threadId !== null && this.inputResolve !== null &&
293
296
  this.pendingTurnId === null && this.pendingTurnCompletion === null &&
294
297
  this.pendingApprovals.size === 0 && !this.hasBlockingUserInput() &&
295
298
  this.pendingPlanCompletion === null && this._pendingPlanInput === null;
@@ -620,6 +623,7 @@ export class CodexProcess extends EventEmitter {
620
623
  if (this.transport) {
621
624
  this.stop();
622
625
  }
626
+ this.launchOptions = options ? { ...options } : undefined;
623
627
  this.resetReadiness();
624
628
  this.prepareLaunch(projectPath, options);
625
629
  this.launchAppServer(projectPath, options);
@@ -642,6 +646,55 @@ export class CodexProcess extends EventEmitter {
642
646
  throw error;
643
647
  }
644
648
  }
649
+ async trustProject(projectPath) {
650
+ const response = await this.request("config/value/write", {
651
+ keyPath: `projects.${JSON.stringify(projectPath)}.trust_level`,
652
+ value: "trusted",
653
+ mergeStrategy: "replace",
654
+ });
655
+ if (response.overriddenMetadata != null)
656
+ throw new Error("Project trust is overridden by a higher-priority configuration");
657
+ }
658
+ async trustProjectAndReload(projectPath) {
659
+ if (readCodexAppServerMode() !== "private")
660
+ throw new Error("Live trust reload requires a private Codex app-server. Shared app-server sessions must be reopened after configuring project trust.");
661
+ if (!this._threadId || this.status !== "idle" || this.pendingTurnId || this.pendingApprovals.size || this.pendingUserInputs.size || this._pendingPlanInput) {
662
+ throw new Error("Wait until the Codex session is idle before trusting this project");
663
+ }
664
+ const options = {
665
+ ...this.launchOptions,
666
+ threadId: this._threadId,
667
+ model: this.model,
668
+ modelReasoningEffort: this._runtimeModelReasoningEffort,
669
+ serviceTier: this._runtimeServiceTier ?? undefined,
670
+ approvalPolicy: this._approvalPolicy,
671
+ approvalsReviewer: this._approvalsReviewer,
672
+ codexPermissionsMode: this._codexPermissionsMode,
673
+ collaborationMode: this._collaborationMode,
674
+ };
675
+ const recoveryEnabled = this.recovery.state.enabled;
676
+ this.reloadingProject = true;
677
+ this.setStatus("starting");
678
+ try {
679
+ await this.trustProject(projectPath);
680
+ await this._completionFetchInFlight;
681
+ // Only this session's transport is replaced. Late events from the old
682
+ // process must not clear the new transport or mark the session stopped.
683
+ this.transport?.removeAllListeners();
684
+ this.stop();
685
+ this.reloadingProject = false;
686
+ this.start(projectPath, options);
687
+ await this.waitUntilReady();
688
+ this.setRecoveryEnabled(recoveryEnabled);
689
+ }
690
+ catch (error) {
691
+ this.reloadingProject = false;
692
+ this.setStatus("idle");
693
+ if (this.inputResolve)
694
+ this.emit("input_ready");
695
+ throw error;
696
+ }
697
+ }
645
698
  async readRateLimits(timeoutMs = 10_000) {
646
699
  return (await this.request("account/rateLimits/read", undefined, timeoutMs));
647
700
  }