@axplusb/kepler 2.0.7 → 2.2.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.
@@ -81,6 +81,7 @@ export async function runHeadless({ instruction, model, timeout = 300, maxCost,
81
81
  let toolCount = 0;
82
82
  let finalContent = '';
83
83
  let totalCost = 0;
84
+ let rateLimit = null;
84
85
 
85
86
  // ── Telemetry collectors ──
86
87
  const toolCalls = []; // { tool, duration_ms, success }
@@ -149,7 +150,12 @@ export async function runHeadless({ instruction, model, timeout = 300, maxCost,
149
150
  if (text) finalContent = text;
150
151
  }
151
152
 
153
+ if (type === 'session_info' && data?.rate_limit) {
154
+ rateLimit = data.rate_limit;
155
+ }
156
+
152
157
  if (type === 'complete') {
158
+ if (data?.rate_limit) rateLimit = data.rate_limit;
153
159
  totalCost = data?.cost || data?.total_cost || 0;
154
160
  if (data?.usage?.total_cost) totalCost = data.usage.total_cost;
155
161
  // Capture token usage
@@ -164,7 +170,13 @@ export async function runHeadless({ instruction, model, timeout = 300, maxCost,
164
170
  }
165
171
 
166
172
  if (type === 'error') {
167
- emit({ type: 'error', error: data?.message || 'Unknown error' });
173
+ emit({
174
+ type: 'error',
175
+ error: data?.message || 'Unknown error',
176
+ code: data?.code,
177
+ retry_after: data?.retry_after,
178
+ rate_limit: data?.rate_limit || null,
179
+ });
168
180
  }
169
181
 
170
182
  // ── Cost guard ──
@@ -202,6 +214,7 @@ export async function runHeadless({ instruction, model, timeout = 300, maxCost,
202
214
  usage,
203
215
  duration_s: Math.round(durationS * 10) / 10,
204
216
  cost_usd: totalCost,
217
+ rate_limit: rateLimit,
205
218
  model: model || 'default',
206
219
  content_length: finalContent.length,
207
220
  });
@@ -40,6 +40,23 @@ function normalizeToolResultContent(output) {
40
40
  }
41
41
  }
42
42
 
43
+ function sanitizeEventValue(value, depth = 0) {
44
+ if (depth > 8) return '[truncated-depth]';
45
+ if (typeof value === 'string') {
46
+ return value.length > 10000 ? value.slice(0, 10000) + '\n[...truncated...]' : value;
47
+ }
48
+ if (value == null || typeof value === 'number' || typeof value === 'boolean') return value;
49
+ if (Array.isArray(value)) return value.slice(0, 100).map(item => sanitizeEventValue(item, depth + 1));
50
+ if (typeof value === 'object') {
51
+ const out = {};
52
+ for (const [key, item] of Object.entries(value).slice(0, 100)) {
53
+ out[key] = sanitizeEventValue(item, depth + 1);
54
+ }
55
+ return out;
56
+ }
57
+ return String(value);
58
+ }
59
+
43
60
  export class JsonlWriter {
44
61
  /**
45
62
  * @param {string} cwd — project working directory
@@ -68,6 +85,7 @@ export class JsonlWriter {
68
85
  this._turnToolResults = []; // [{tool_use_id, content, is_error}, ...]
69
86
  this._turnUsage = null;
70
87
  this._turnModel = null;
88
+ this._pendingKeplerEvents = [];
71
89
 
72
90
  // Git branch (captured once at construction)
73
91
  this._gitBranch = this._detectGitBranch();
@@ -83,6 +101,11 @@ export class JsonlWriter {
83
101
  this.sessionId = id;
84
102
  this._transcriptPath = path.join(this.projectDir, `${id}.jsonl`);
85
103
  this._ready = true;
104
+ if (this._pendingKeplerEvents.length > 0) {
105
+ const pending = this._pendingKeplerEvents;
106
+ this._pendingKeplerEvents = [];
107
+ for (const event of pending) this.writeKeplerEvent(event);
108
+ }
86
109
  // Flush any buffered entries now that we have a path
87
110
  if (this._buffer.length > 0) this._flush();
88
111
  }
@@ -119,6 +142,33 @@ export class JsonlWriter {
119
142
  this.lastUuid = uuid;
120
143
  }
121
144
 
145
+ /**
146
+ * Write a Kepler UI/SSE event for exact-ish terminal replay on /resume.
147
+ * This is a Kepler extension; cc-lens readers should ignore unknown types.
148
+ */
149
+ writeKeplerEvent(event) {
150
+ if (!event || !event.type) return;
151
+ const sanitized = sanitizeEventValue({
152
+ type: event.type,
153
+ data: event.data || {},
154
+ });
155
+
156
+ if (!this.sessionId) {
157
+ this._pendingKeplerEvents.push(sanitized);
158
+ return;
159
+ }
160
+
161
+ const entry = {
162
+ type: 'kepler_event',
163
+ timestamp: new Date().toISOString(),
164
+ cwd: this.cwd,
165
+ sessionId: this.sessionId,
166
+ version: this.version,
167
+ event: sanitized,
168
+ };
169
+ this._appendEntry(entry);
170
+ }
171
+
122
172
  /**
123
173
  * Accumulate content during streaming (call on content/content_partial events).
124
174
  */