rudel 0.1.16 → 0.1.17

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 (2) hide show
  1. package/dist/cli.js +53 -8
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -1889,7 +1889,7 @@ async function run(app, inputs, context) {
1889
1889
  // package.json
1890
1890
  var package_default = {
1891
1891
  name: "rudel",
1892
- version: "0.1.16",
1892
+ version: "0.1.17",
1893
1893
  type: "module",
1894
1894
  description: "CLI for the Coding Agent Analytics Platform rudel.ai",
1895
1895
  license: "MIT",
@@ -1924,7 +1924,7 @@ var package_default = {
1924
1924
  build: "bun build src/bin/cli.ts --outdir dist --target node",
1925
1925
  prepack: "bun run build",
1926
1926
  test: "bun test",
1927
- "test:integration": "bun test ./src/__tests__/api-upload.integration.ts ./src/__tests__/auth-verify.integration.ts ./src/__tests__/auth.e2e.integration.ts"
1927
+ "test:integration": "bun test ./src/__tests__/api-upload.integration.ts ./src/__tests__/uploader-rate-limit.integration.ts ./src/__tests__/auth-verify.integration.ts ./src/__tests__/auth.e2e.integration.ts"
1928
1928
  },
1929
1929
  dependencies: {
1930
1930
  "@clack/prompts": "^1.0.1",
@@ -6385,7 +6385,7 @@ class ClaudeCodeAdapter {
6385
6385
  await ingestRudelClaudeSessions(ingestor, [row]);
6386
6386
  }
6387
6387
  buildRow(input, context) {
6388
- const now = new Date().toISOString().replace("Z", "");
6388
+ const now = context.ingestedAt.toISOString().replace("Z", "");
6389
6389
  const subagents = {};
6390
6390
  if (input.subagents) {
6391
6391
  for (const sub of input.subagents) {
@@ -7717,7 +7717,7 @@ class CodexAdapter {
7717
7717
  await ingestRudelCodexSessions(ingestor, [row]);
7718
7718
  }
7719
7719
  buildRow(input, context) {
7720
- const now = new Date().toISOString().replace("Z", "");
7720
+ const now = context.ingestedAt.toISOString().replace("Z", "");
7721
7721
  const timestamps = this.extractTimestamps(input.content);
7722
7722
  return {
7723
7723
  session_date: timestamps ? toClickHouseDateTime(timestamps.sessionDate) : now,
@@ -13157,9 +13157,18 @@ var SessionTagSchema = exports_external.enum([
13157
13157
  "tests",
13158
13158
  "other"
13159
13159
  ]);
13160
+ var INGEST_AGGREGATE_CONTENT_MAX_BYTES = 128 * 1024 * 1024;
13161
+ var INGEST_MAX_SUBAGENT_COUNT = 512;
13162
+ var INGEST_LIMIT_REASONS = {
13163
+ requestLimit: "request_limit",
13164
+ byteLimit: "byte_limit",
13165
+ sessionLimit: "session_limit",
13166
+ transcriptTooLarge: "transcript_too_large"
13167
+ };
13168
+ var INGEST_CONTENT_MAX_CODE_UNITS = 160 * 1024 * 1024;
13160
13169
  var SubagentFileSchema = exports_external.object({
13161
- agentId: exports_external.string(),
13162
- content: exports_external.string()
13170
+ agentId: exports_external.string().max(200),
13171
+ content: exports_external.string().max(INGEST_CONTENT_MAX_CODE_UNITS)
13163
13172
  });
13164
13173
  var IngestSessionInputSchema = exports_external.object({
13165
13174
  source: SourceSchema.default("claude_code"),
@@ -13171,8 +13180,8 @@ var IngestSessionInputSchema = exports_external.object({
13171
13180
  gitBranch: exports_external.string().max(200).optional(),
13172
13181
  gitSha: exports_external.string().max(200).optional(),
13173
13182
  tag: SessionTagSchema.optional(),
13174
- content: exports_external.string(),
13175
- subagents: exports_external.array(SubagentFileSchema).optional(),
13183
+ content: exports_external.string().max(INGEST_CONTENT_MAX_CODE_UNITS),
13184
+ subagents: exports_external.array(SubagentFileSchema).max(INGEST_MAX_SUBAGENT_COUNT).refine((subagents) => new Set(subagents.map((subagent) => subagent.agentId)).size === subagents.length, { message: "Subagent agentId values must be unique" }).optional(),
13176
13185
  organizationId: exports_external.string().max(200).optional(),
13177
13186
  client_surface: ProductAnalyticsClientSurfaceSchema.optional(),
13178
13187
  upload_mode: ProductAnalyticsUploadModeSchema.optional(),
@@ -18214,6 +18223,13 @@ function formatUploadError(error) {
18214
18223
  }
18215
18224
  if (isRateLimited(error)) {
18216
18225
  const data = getErrorData(error);
18226
+ const isRequestLimit = data.reason === INGEST_LIMIT_REASONS.requestLimit;
18227
+ if (isRequestLimit || data.reason === INGEST_LIMIT_REASONS.byteLimit) {
18228
+ const limit2 = data.limit === null ? null : isRequestLimit ? `${data.limit} requests` : `${formatMebibytes(data.limit)} MiB`;
18229
+ const detail = limit2 && data.windowSeconds ? ` (${limit2} per ${Math.round(data.windowSeconds / 60)} min)` : "";
18230
+ const kind = isRequestLimit ? "request" : "byte";
18231
+ return `Ingest ${kind} limit reached${detail}. Wait and retry with: rudel upload --retry`;
18232
+ }
18217
18233
  const windowMin = data?.windowSeconds ? Math.round(data.windowSeconds / 60) : 60;
18218
18234
  const limit = data?.limit ?? "unknown";
18219
18235
  return `Rate limit reached (${limit} sessions per ${windowMin} min). Wait and retry with: rudel upload --retry`;
@@ -18222,6 +18238,10 @@ function formatUploadError(error) {
18222
18238
  return "This session ID is already owned by another organization member. Upload it from the original member account or use a different session ID.";
18223
18239
  }
18224
18240
  if (isPayloadTooLarge(error)) {
18241
+ const data = getErrorData(error);
18242
+ if (data.reason === INGEST_LIMIT_REASONS.transcriptTooLarge) {
18243
+ return formatTranscriptTooLargeError(data.actualBytes, data.maxBytes);
18244
+ }
18225
18245
  return formatPayloadTooLargeError(error);
18226
18246
  }
18227
18247
  if (isServerError(error)) {
@@ -18261,8 +18281,10 @@ function getErrorData(error) {
18261
18281
  const data = isRecord(error.data) ? error.data : null;
18262
18282
  return {
18263
18283
  authMessage: getStringField(data, "authMessage"),
18284
+ actualBytes: getNumberField(data, "actualBytes"),
18264
18285
  code: getStringField(data, "code"),
18265
18286
  limit: getNumberField(data, "limit"),
18287
+ maxBytes: getNumberField(data, "maxBytes"),
18266
18288
  reason: getStringField(data, "reason"),
18267
18289
  tryAgainIn: getNumberField(data, "tryAgainIn"),
18268
18290
  windowSeconds: getNumberField(data, "windowSeconds")
@@ -18292,6 +18314,15 @@ function formatWait(milliseconds) {
18292
18314
  return `${hours} hr`;
18293
18315
  }
18294
18316
  async function uploadSession(request, config) {
18317
+ const maxAggregateBytes = config.maxAggregateBytes ?? INGEST_AGGREGATE_CONTENT_MAX_BYTES;
18318
+ const aggregateBytes = getUploadAggregateBytes(request);
18319
+ if (aggregateBytes > maxAggregateBytes) {
18320
+ return {
18321
+ success: false,
18322
+ error: formatTranscriptTooLargeError(aggregateBytes, maxAggregateBytes),
18323
+ attempts: 0
18324
+ };
18325
+ }
18295
18326
  const link = new RPCLink({
18296
18327
  url: config.endpoint,
18297
18328
  headers: config.authType === "api-key" ? { "x-api-key": config.token } : { Authorization: `Bearer ${config.token}` }
@@ -18330,6 +18361,20 @@ async function uploadSession(request, config) {
18330
18361
  attempts: MAX_ATTEMPTS
18331
18362
  };
18332
18363
  }
18364
+ function getUploadAggregateBytes(request) {
18365
+ return Buffer.byteLength(request.content, "utf8") + (request.subagents ?? []).reduce((total, subagent) => total + Buffer.byteLength(subagent.content, "utf8"), 0);
18366
+ }
18367
+ function formatTranscriptTooLargeError(actualBytes, maxBytes) {
18368
+ if (actualBytes === null || maxBytes === null) {
18369
+ return "Session transcript payload exceeds the per-session limit. Reduce the transcript/subagent payload before retrying.";
18370
+ }
18371
+ const actualText = `${formatMebibytes(actualBytes)} MiB`;
18372
+ const limitText = `the ${formatMebibytes(maxBytes)} MiB per-session limit`;
18373
+ return `Session transcript payload is ${actualText}, above ${limitText}. Reduce the transcript/subagent payload before retrying.`;
18374
+ }
18375
+ function formatMebibytes(bytes) {
18376
+ return (bytes / (1024 * 1024)).toFixed(2);
18377
+ }
18333
18378
 
18334
18379
  // src/commands/enable.ts
18335
18380
  async function runEnable() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rudel",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "type": "module",
5
5
  "description": "CLI for the Coding Agent Analytics Platform rudel.ai",
6
6
  "license": "MIT",
@@ -35,7 +35,7 @@
35
35
  "build": "bun build src/bin/cli.ts --outdir dist --target node",
36
36
  "prepack": "bun run build",
37
37
  "test": "bun test",
38
- "test:integration": "bun test ./src/__tests__/api-upload.integration.ts ./src/__tests__/auth-verify.integration.ts ./src/__tests__/auth.e2e.integration.ts"
38
+ "test:integration": "bun test ./src/__tests__/api-upload.integration.ts ./src/__tests__/uploader-rate-limit.integration.ts ./src/__tests__/auth-verify.integration.ts ./src/__tests__/auth.e2e.integration.ts"
39
39
  },
40
40
  "dependencies": {
41
41
  "@clack/prompts": "^1.0.1",