@saccolabs/pi-claude-cli 0.4.4 → 0.4.5

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/index.ts CHANGED
@@ -22,6 +22,44 @@ process.on("exit", killAllProcesses);
22
22
 
23
23
  const PROVIDER_ID = "pi-claude-cli";
24
24
 
25
+ /**
26
+ * Status key carrying account rate-limit state to the front-end. Neutral
27
+ * (not pidex-specific) because any pi front-end can read it.
28
+ */
29
+ const RATE_LIMIT_STATUS_KEY = "claude-rate-limit";
30
+
31
+ /**
32
+ * The stream runs deep inside streamSimple, which has no ExtensionContext,
33
+ * so the ctx handed to session_start is kept for its `ui.setStatus`.
34
+ */
35
+ let uiContext:
36
+ { ui?: { setStatus?(key: string, text?: string): void } } | undefined;
37
+ /** Last payload pushed — the CLI repeats this event on every turn. */
38
+ let lastRateLimitJson: string | undefined;
39
+
40
+ function publishRateLimit(info: Record<string, unknown>): void {
41
+ const setStatus = uiContext?.ui?.setStatus;
42
+ if (typeof setStatus !== "function") return;
43
+ const payload = JSON.stringify({
44
+ status: info.status,
45
+ resetsAt: info.resetsAt,
46
+ rateLimitType: info.rateLimitType,
47
+ overageStatus: info.overageStatus,
48
+ isUsingOverage: info.isUsingOverage === true,
49
+ observedAt: Math.floor(Date.now() / 1000),
50
+ });
51
+ // Push only on change: the event repeats every turn, and a status that
52
+ // rewrites itself constantly is noise for whatever renders it.
53
+ const withoutObservedAt = payload.replace(/,"observedAt":\d+/, "");
54
+ if (withoutObservedAt === lastRateLimitJson) return;
55
+ lastRateLimitJson = withoutObservedAt;
56
+ try {
57
+ setStatus.call(uiContext!.ui, RATE_LIMIT_STATUS_KEY, payload);
58
+ } catch {
59
+ /* never break a turn over a status push */
60
+ }
61
+ }
62
+
25
63
  let mcpConfigPath: string | undefined;
26
64
  let mcpConfigResolved = false;
27
65
 
@@ -91,7 +129,9 @@ export default function (pi: ExtensionAPI) {
91
129
 
92
130
  // Ensure all registered tools are active so pi can execute them.
93
131
  // Some tools (find, grep, ls) are registered but not activated by default.
94
- pi.on("session_start", async () => {
132
+ pi.on("session_start", async (_event: unknown, ctx: unknown) => {
133
+ uiContext = ctx as typeof uiContext;
134
+ lastRateLimitJson = undefined;
95
135
  const allTools = pi.getAllTools();
96
136
  if (Array.isArray(allTools)) {
97
137
  pi.setActiveTools(allTools.map((t: any) => t.name));
@@ -107,6 +147,7 @@ export default function (pi: ExtensionAPI) {
107
147
  return streamViaCli(model, context, {
108
148
  ...options,
109
149
  mcpConfigPath: configPath,
150
+ onRateLimit: publishRateLimit,
110
151
  });
111
152
  };
112
153
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saccolabs/pi-claude-cli",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
4
4
  "description": "Pi coding agent extension that routes LLM calls through the Claude Code CLI",
5
5
  "main": "index.ts",
6
6
  "keywords": [
package/src/provider.ts CHANGED
@@ -55,6 +55,8 @@ const INACTIVITY_TIMEOUT_MS =
55
55
  type StreamViaCLiOptions = SimpleStreamOptions & {
56
56
  cwd?: string;
57
57
  mcpConfigPath?: string;
58
+ /** Called with account rate-limit state as the CLI reports it. */
59
+ onRateLimit?: (info: Record<string, unknown>) => void;
58
60
  };
59
61
 
60
62
  /**
@@ -296,6 +298,18 @@ export function streamViaCli(
296
298
  rl.close();
297
299
  return; // Don't process further -- done event already pushed by event bridge
298
300
  }
301
+ } else if (msg.type === "rate_limit_event") {
302
+ // Account-level state, not turn content: hand it to the host so a
303
+ // front-end can surface the window and its reset. Never touches
304
+ // the assistant message.
305
+ const info = (msg as any).rate_limit_info;
306
+ if (info && typeof info === "object") {
307
+ try {
308
+ options?.onRateLimit?.(info);
309
+ } catch {
310
+ /* a status push must never break a turn */
311
+ }
312
+ }
299
313
  } else if (msg.type === "assistant") {
300
314
  // Complete-block envelopes: marker text for CLI-side tools that
301
315
  // would otherwise be invisible between cycles.
package/src/types.ts CHANGED
@@ -43,6 +43,24 @@ export interface ClaudeAssistantEnvelope {
43
43
  };
44
44
  }
45
45
 
46
+ /**
47
+ * Emitted when the API reports rate-limit state for the account. Carries the
48
+ * window and its reset, not a utilization percentage — the percentages the
49
+ * Claude Code TUI shows come from `anthropic-ratelimit-unified-*` response
50
+ * headers, which the CLI consumes in-process and does not forward here.
51
+ */
52
+ export interface ClaudeRateLimitEvent {
53
+ type: "rate_limit_event";
54
+ rate_limit_info?: {
55
+ status?: string;
56
+ resetsAt?: number;
57
+ rateLimitType?: string;
58
+ overageStatus?: string;
59
+ overageDisabledReason?: string;
60
+ isUsingOverage?: boolean;
61
+ };
62
+ }
63
+
46
64
  /** Tool results the CLI feeds back between cycles (top-level only). */
47
65
  export interface ClaudeUserEnvelope {
48
66
  type: "user";
@@ -81,7 +99,8 @@ export type NdjsonMessage =
81
99
  | ClaudeSystemMessage
82
100
  | ClaudeControlRequest
83
101
  | ClaudeAssistantEnvelope
84
- | ClaudeUserEnvelope;
102
+ | ClaudeUserEnvelope
103
+ | ClaudeRateLimitEvent;
85
104
 
86
105
  // Claude API event types (inside stream_event wrapper)
87
106